From 637dce83e29fe23735256f25f0c9881ed2546520 Mon Sep 17 00:00:00 2001 From: nknize Date: Fri, 1 Apr 2016 11:03:11 -0500 Subject: [PATCH] LUCENE-7152: Refactor GeoUtils from lucene-spatial to core module. --- lucene/CHANGES.txt | 3 + .../java/org/apache/lucene/geo/GeoUtils.java | 94 +++++++++ .../org/apache/lucene/geo/package-info.java | 21 ++ .../apache/lucene/document/LatLonPoint.java | 2 +- .../LatLonPointDistanceComparator.java | 4 +- .../document/LatLonPointDistanceQuery.java | 8 +- .../document/LatLonPointInPolygonQuery.java | 2 +- .../lucene/document/LatLonPointSortField.java | 2 +- .../geopoint/document/GeoPointField.java | 2 +- .../search/GeoPointDistanceQuery.java | 4 +- .../search/GeoPointDistanceQueryImpl.java | 5 +- .../geopoint/search/GeoPointInBBoxQuery.java | 2 +- .../search/GeoPointInPolygonQuery.java | 2 +- .../search/GeoPointMultiTermQuery.java | 2 +- .../lucene/spatial/util/GeoEncodingUtils.java | 4 +- .../apache/lucene/spatial/util/GeoRect.java | 121 ++++++++++++ .../apache/lucene/spatial/util/GeoUtils.java | 179 ------------------ .../apache/lucene/spatial/util/Polygon.java | 20 +- .../spatial/util/BaseGeoPointTestCase.java | 1 + .../lucene/spatial/util/GeoTestUtil.java | 5 +- .../lucene/spatial/util/TestGeoUtils.java | 31 +-- 21 files changed, 280 insertions(+), 234 deletions(-) create mode 100644 lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java create mode 100644 lucene/core/src/java/org/apache/lucene/geo/package-info.java delete mode 100644 lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 33bf3a59695..a35f83cdce7 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -15,6 +15,9 @@ New Features API Changes +* LUCENE-7152: Refactor GeoUtils from lucene-spatial package to + core (Nick Knize) + * LUCENE-7141: Switch OfflineSorter's ByteSequencesReader to BytesRefIterator (Mike McCandless) diff --git a/lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java b/lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java new file mode 100644 index 00000000000..c11dfe185bd --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java @@ -0,0 +1,94 @@ +package org.apache.lucene.geo; + +import static org.apache.lucene.util.SloppyMath.TO_RADIANS; +import static org.apache.lucene.util.SloppyMath.cos; + +/* + * 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. + */ + +/** + * Basic reusable geo-spatial utility methods + * + * @lucene.experimental + */ +public final class GeoUtils { + /** Minimum longitude value. */ + public static final double MIN_LON_INCL = -180.0D; + + /** Maximum longitude value. */ + public static final double MAX_LON_INCL = 180.0D; + + /** Minimum latitude value. */ + public static final double MIN_LAT_INCL = -90.0D; + + /** Maximum latitude value. */ + public static final double MAX_LAT_INCL = 90.0D; + + /** min longitude value in radians */ + public static final double MIN_LON_RADIANS = TO_RADIANS * MIN_LON_INCL; + /** min latitude value in radians */ + public static final double MIN_LAT_RADIANS = TO_RADIANS * MIN_LAT_INCL; + /** max longitude value in radians */ + public static final double MAX_LON_RADIANS = TO_RADIANS * MAX_LON_INCL; + /** max latitude value in radians */ + public static final double MAX_LAT_RADIANS = TO_RADIANS * MAX_LAT_INCL; + + // WGS84 earth-ellipsoid parameters + /** mean earth axis in meters */ + // see http://earth-info.nga.mil/GandG/publications/tr8350.2/wgs84fin.pdf + public static final double EARTH_MEAN_RADIUS_METERS = 6_371_008.7714; + + // No instance: + private GeoUtils() { + } + + /** validates latitude value is within standard +/-90 coordinate bounds */ + public static void checkLatitude(double latitude) { + if (Double.isNaN(latitude) || latitude < MIN_LAT_INCL || latitude > MAX_LAT_INCL) { + throw new IllegalArgumentException("invalid latitude " + latitude + "; must be between " + MIN_LAT_INCL + " and " + MAX_LAT_INCL); + } + } + + /** validates longitude value is within standard +/-180 coordinate bounds */ + public static void checkLongitude(double longitude) { + if (Double.isNaN(longitude) || longitude < MIN_LON_INCL || longitude > MAX_LON_INCL) { + throw new IllegalArgumentException("invalid longitude " + longitude + "; must be between " + MIN_LON_INCL + " and " + MAX_LON_INCL); + } + } + + // some sloppyish stuff, do we really need this to be done in a sloppy way? + // unless it is performance sensitive, we should try to remove. + private static final double PIO2 = Math.PI / 2D; + + /** + * Returns the trigonometric sine of an angle converted as a cos operation. + *

+ * Note that this is not quite right... e.g. sin(0) != 0 + *

+ * Special cases: + *

+ * @param a an angle, in radians. + * @return the sine of the argument. + * @see Math#sin(double) + */ + // TODO: deprecate/remove this? at least its no longer public. + public static double sloppySin(double a) { + return cos(a - PIO2); + } +} diff --git a/lucene/core/src/java/org/apache/lucene/geo/package-info.java b/lucene/core/src/java/org/apache/lucene/geo/package-info.java new file mode 100644 index 00000000000..8d9afc9e4d2 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/geo/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Geospatial Utility Implementations for Lucene Core + */ +package org.apache.lucene.geo; \ No newline at end of file diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java index 70149b641a5..af014cba3c6 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java @@ -16,6 +16,7 @@ */ package org.apache.lucene.document; +import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.NumericUtils; import org.apache.lucene.index.DocValuesType; @@ -28,7 +29,6 @@ import org.apache.lucene.search.FieldDoc; import org.apache.lucene.search.PointRangeQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.SortField; -import org.apache.lucene.spatial.util.GeoUtils; import org.apache.lucene.spatial.util.Polygon; /** diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java index e818e1d2baf..0ed85adf09e 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java @@ -27,7 +27,7 @@ import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.LeafFieldComparator; import org.apache.lucene.search.Scorer; import org.apache.lucene.spatial.util.GeoRect; -import org.apache.lucene.spatial.util.GeoUtils; +import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.util.SloppyMath; /** @@ -83,7 +83,7 @@ class LatLonPointDistanceComparator extends FieldComparator implements L // sampling if we get called way too much: don't make gobs of bounding // boxes if comparator hits a worst case order (e.g. backwards distance order) if (setBottomCounter < 1024 || (setBottomCounter & 0x3F) == 0x3F) { - GeoRect box = GeoUtils.circleToBBox(latitude, longitude, haversin2(bottom)); + GeoRect box = GeoRect.fromPointDistance(latitude, longitude, haversin2(bottom)); // pre-encode our box to our integer encoding, so we don't have to decode // to double values for uncompetitive hits. This has some cost! minLat = LatLonPoint.encodeLatitude(box.minLat); diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java index 04d37233c95..29347189f8b 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java @@ -36,7 +36,7 @@ import org.apache.lucene.search.Scorer; import org.apache.lucene.search.TwoPhaseIterator; import org.apache.lucene.search.Weight; import org.apache.lucene.spatial.util.GeoRect; -import org.apache.lucene.spatial.util.GeoUtils; +import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.util.BitSet; import org.apache.lucene.util.DocIdSetBuilder; import org.apache.lucene.util.FixedBitSet; @@ -71,7 +71,7 @@ final class LatLonPointDistanceQuery extends Query { @Override public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException { - GeoRect box = GeoUtils.circleToBBox(latitude, longitude, radiusMeters); + GeoRect box = GeoRect.fromPointDistance(latitude, longitude, radiusMeters); // create bounding box(es) for the distance range // these are pre-encoded with LatLonPoint's encoding final byte minLat[] = new byte[Integer.BYTES]; @@ -108,7 +108,7 @@ final class LatLonPointDistanceQuery extends Query { maxPartialDistance = Double.POSITIVE_INFINITY; } - final double axisLat = GeoUtils.axisLat(latitude, radiusMeters); + final double axisLat = GeoRect.axisLat(latitude, radiusMeters); return new ConstantScoreWeight(this) { @@ -196,7 +196,7 @@ final class LatLonPointDistanceQuery extends Query { double latMax = LatLonPoint.decodeLatitude(maxPackedValue, 0); double lonMax = LatLonPoint.decodeLongitude(maxPackedValue, Integer.BYTES); - if ((longitude < lonMin || longitude > lonMax) && (axisLat+GeoUtils.AXISLAT_ERROR < latMin || axisLat-GeoUtils.AXISLAT_ERROR > latMax)) { + if ((longitude < lonMin || longitude > lonMax) && (axisLat+GeoRect.AXISLAT_ERROR < latMin || axisLat-GeoRect.AXISLAT_ERROR > latMax)) { // circle not fully inside / crossing axis if (SloppyMath.haversinMeters(latitude, longitude, latMin, lonMin) > radiusMeters && SloppyMath.haversinMeters(latitude, longitude, latMin, lonMax) > radiusMeters && diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java index 2875e2f0423..56e906bc59d 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java @@ -84,7 +84,7 @@ final class LatLonPointInPolygonQuery extends Query { // bounding box over all polygons, this can speed up tree intersection/cheaply improve approximation for complex multi-polygons // these are pre-encoded with LatLonPoint's encoding - final GeoRect box = Polygon.getBoundingBox(polygons); + final GeoRect box = GeoRect.fromPolygon(polygons); final byte minLat[] = new byte[Integer.BYTES]; final byte maxLat[] = new byte[Integer.BYTES]; final byte minLon[] = new byte[Integer.BYTES]; diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java index 709122977d5..c8864384743 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java @@ -20,7 +20,7 @@ import java.io.IOException; import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.SortField; -import org.apache.lucene.spatial.util.GeoUtils; +import org.apache.lucene.geo.GeoUtils; /** * Sorts by distance from an origin location. 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 378e513d8a1..8b1483abbae 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 @@ -23,7 +23,7 @@ import org.apache.lucene.document.FieldType; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.spatial.util.GeoEncodingUtils; -import org.apache.lucene.spatial.util.GeoUtils; +import org.apache.lucene.geo.GeoUtils; /** *

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 cdd0d6d8e69..c7ac6eb91fc 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 @@ -22,7 +22,7 @@ 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.GeoRect; -import org.apache.lucene.spatial.util.GeoUtils; +import org.apache.lucene.geo.GeoUtils; /** Implements a simple point distance query on a GeoPoint field. This is based on * {@link GeoPointInBBoxQuery} and is implemented using a two phase approach. First, @@ -80,7 +80,7 @@ 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(checkLatitude(centerLat), checkLongitude(centerLon), checkRadius(radiusMeters)), centerLat, centerLon, radiusMeters); + this(field, termEncoding, GeoRect.fromPointDistance(centerLat, centerLon, checkRadius(radiusMeters)), centerLat, centerLon, radiusMeters); } private GeoPointDistanceQuery(final String field, final TermEncoding termEncoding, final GeoRect bbox, diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java index dea2be80f81..46dcce9553f 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java @@ -19,7 +19,6 @@ package org.apache.lucene.spatial.geopoint.search; import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding; import org.apache.lucene.spatial.util.GeoRect; -import org.apache.lucene.spatial.util.GeoUtils; import org.apache.lucene.util.SloppyMath; /** Package private implementation for the public facing GeoPointDistanceQuery delegate class. @@ -50,7 +49,7 @@ final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl { } else { maxPartialDistance = Double.POSITIVE_INFINITY; } - axisLat = GeoUtils.axisLat(distanceQuery.centerLat, distanceQuery.radiusMeters); + axisLat = GeoRect.axisLat(distanceQuery.centerLat, distanceQuery.radiusMeters); } @Override @@ -76,7 +75,7 @@ final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl { minLat > GeoPointDistanceQueryImpl.this.maxLat || minLon > GeoPointDistanceQueryImpl.this.maxLon) { return false; - } else if ((centerLon < minLon || centerLon > maxLon) && (axisLat+GeoUtils.AXISLAT_ERROR < minLat || axisLat-GeoUtils.AXISLAT_ERROR > maxLat)) { + } else if ((centerLon < minLon || centerLon > maxLon) && (axisLat+GeoRect.AXISLAT_ERROR < minLat || axisLat-GeoRect.AXISLAT_ERROR > maxLat)) { if (SloppyMath.haversinMeters(distanceQuery.centerLat, centerLon, minLat, minLon) > distanceQuery.radiusMeters && SloppyMath.haversinMeters(distanceQuery.centerLat, centerLon, minLat, maxLon) > distanceQuery.radiusMeters && SloppyMath.haversinMeters(distanceQuery.centerLat, centerLon, maxLat, minLon) > distanceQuery.radiusMeters && 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 4229b28a637..f30950e3125 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 @@ -23,7 +23,7 @@ import org.apache.lucene.search.FieldValueQuery; import org.apache.lucene.search.LegacyNumericRangeQuery; import org.apache.lucene.search.Query; import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding; -import org.apache.lucene.spatial.util.GeoUtils; +import org.apache.lucene.geo.GeoUtils; /** Implements a simple bounding box query on a GeoPoint field. This is inspired by * {@link LegacyNumericRangeQuery} and is implemented using a diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQuery.java index 4e6bdcee585..0d29d252b5a 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQuery.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQuery.java @@ -82,7 +82,7 @@ public final class GeoPointInPolygonQuery extends GeoPointInBBoxQuery { * that fall within or on the boundary of the polygon defined by the input parameters. */ public GeoPointInPolygonQuery(String field, TermEncoding termEncoding, Polygon... polygons) { - this(field, termEncoding, Polygon.getBoundingBox(polygons), polygons); + this(field, termEncoding, GeoRect.fromPolygon(polygons), polygons); } // internal constructor diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java index ec08475035e..f1f53e33f3f 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java @@ -29,7 +29,7 @@ import org.apache.lucene.spatial.geopoint.document.GeoPointField; import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding; import org.apache.lucene.spatial.util.GeoEncodingUtils; import org.apache.lucene.spatial.util.GeoRelationUtils; -import org.apache.lucene.spatial.util.GeoUtils; +import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.util.SloppyMath; /** diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java index 7bed892bc81..d2141d93c15 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java @@ -21,8 +21,8 @@ import org.apache.lucene.util.BitUtil; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; -import static org.apache.lucene.spatial.util.GeoUtils.MIN_LON_INCL; -import static org.apache.lucene.spatial.util.GeoUtils.MIN_LAT_INCL; +import static org.apache.lucene.geo.GeoUtils.MIN_LON_INCL; +import static org.apache.lucene.geo.GeoUtils.MIN_LAT_INCL; /** * Basic reusable geopoint encoding methods diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java index 2dbba6d6011..391f6d78612 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java @@ -16,6 +16,27 @@ */ package org.apache.lucene.spatial.util; +import org.apache.lucene.geo.GeoUtils; + +import static java.lang.Math.PI; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.Math.toDegrees; +import static java.lang.Math.toRadians; +import static org.apache.lucene.geo.GeoUtils.checkLatitude; +import static org.apache.lucene.geo.GeoUtils.checkLongitude; +import static org.apache.lucene.geo.GeoUtils.MAX_LAT_INCL; +import static org.apache.lucene.geo.GeoUtils.MIN_LAT_INCL; +import static org.apache.lucene.geo.GeoUtils.MAX_LAT_RADIANS; +import static org.apache.lucene.geo.GeoUtils.MAX_LON_RADIANS; +import static org.apache.lucene.geo.GeoUtils.MIN_LAT_RADIANS; +import static org.apache.lucene.geo.GeoUtils.MIN_LON_RADIANS; +import static org.apache.lucene.geo.GeoUtils.EARTH_MEAN_RADIUS_METERS; +import static org.apache.lucene.geo.GeoUtils.sloppySin; +import static org.apache.lucene.util.SloppyMath.TO_DEGREES; +import static org.apache.lucene.util.SloppyMath.asin; +import static org.apache.lucene.util.SloppyMath.cos; + /** Represents a lat/lon rectangle. */ public class GeoRect { /** maximum longitude value (in degrees) */ @@ -67,4 +88,104 @@ public class GeoRect { public boolean crossesDateline() { return maxLon < minLon; } + + /** Compute Bounding Box for a circle using WGS-84 parameters */ + public static GeoRect fromPointDistance(final double centerLat, final double centerLon, final double radiusMeters) { + checkLatitude(centerLat); + checkLongitude(centerLon); + final double radLat = toRadians(centerLat); + final double radLon = toRadians(centerLon); + // LUCENE-7143 + double radDistance = (radiusMeters + 7E-2) / EARTH_MEAN_RADIUS_METERS; + double minLat = radLat - radDistance; + double maxLat = radLat + radDistance; + double minLon; + double maxLon; + + if (minLat > MIN_LAT_RADIANS && maxLat < MAX_LAT_RADIANS) { + double deltaLon = asin(sloppySin(radDistance) / cos(radLat)); + minLon = radLon - deltaLon; + if (minLon < MIN_LON_RADIANS) { + minLon += 2d * PI; + } + maxLon = radLon + deltaLon; + if (maxLon > MAX_LON_RADIANS) { + maxLon -= 2d * PI; + } + } else { + // a pole is within the distance + minLat = max(minLat, MIN_LAT_RADIANS); + maxLat = min(maxLat, MAX_LAT_RADIANS); + minLon = MIN_LON_RADIANS; + maxLon = MAX_LON_RADIANS; + } + + return new GeoRect(toDegrees(minLat), toDegrees(maxLat), toDegrees(minLon), toDegrees(maxLon)); + } + + /** maximum error from {@link #axisLat(double, double)}. logic must be prepared to handle this */ + public static final double AXISLAT_ERROR = 0.1D / EARTH_MEAN_RADIUS_METERS * TO_DEGREES; + + /** + * Calculate the latitude of a circle's intersections with its bbox meridians. + *

+ * NOTE: the returned value will be +/- {@link #AXISLAT_ERROR} of the actual value. + * @param centerLat The latitude of the circle center + * @param radiusMeters The radius of the circle in meters + * @return A latitude + */ + public static double axisLat(double centerLat, double radiusMeters) { + // A spherical triangle with: + // r is the radius of the circle in radians + // l1 is the latitude of the circle center + // l2 is the latitude of the point at which the circle intersect's its bbox longitudes + // We know r is tangent to the bbox meridians at l2, therefore it is a right angle. + // So from the law of cosines, with the angle of l1 being 90, we have: + // cos(l1) = cos(r) * cos(l2) + sin(r) * sin(l2) * cos(90) + // The second part cancels out because cos(90) == 0, so we have: + // cos(l1) = cos(r) * cos(l2) + // Solving for l2, we get: + // l2 = acos( cos(l1) / cos(r) ) + // We ensure r is in the range (0, PI/2) and l1 in the range (0, PI/2]. This means we + // cannot divide by 0, and we will always get a positive value in the range [0, 1) as + // the argument to arc cosine, resulting in a range (0, PI/2]. + final double PIO2 = Math.PI / 2D; + double l1 = toRadians(centerLat); + double r = (radiusMeters + 7E-2) / EARTH_MEAN_RADIUS_METERS; + + // if we are within radius range of a pole, the lat is the pole itself + if (Math.abs(l1) + r >= MAX_LAT_RADIANS) { + return centerLat >= 0 ? MAX_LAT_INCL : MIN_LAT_INCL; + } + + // adjust l1 as distance from closest pole, to form a right triangle with bbox meridians + // and ensure it is in the range (0, PI/2] + l1 = centerLat >= 0 ? PIO2 - l1 : l1 + PIO2; + + double l2 = Math.acos(Math.cos(l1) / Math.cos(r)); + assert !Double.isNaN(l2); + + // now adjust back to range [-pi/2, pi/2], ie latitude in radians + l2 = centerLat >= 0 ? PIO2 - l2 : l2 - PIO2; + + return toDegrees(l2); + } + + /** Returns the bounding box over an array of polygons */ + public static GeoRect fromPolygon(Polygon[] polygons) { + // compute bounding box + double minLat = Double.POSITIVE_INFINITY; + double maxLat = Double.NEGATIVE_INFINITY; + double minLon = Double.POSITIVE_INFINITY; + double maxLon = Double.NEGATIVE_INFINITY; + + for (int i = 0;i < polygons.length; i++) { + minLat = Math.min(polygons[i].minLat, minLat); + maxLat = Math.max(polygons[i].maxLat, maxLat); + minLon = Math.min(polygons[i].minLon, minLon); + maxLon = Math.max(polygons[i].maxLon, maxLon); + } + + return new GeoRect(minLat, maxLat, minLon, maxLon); + } } diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java deleted file mode 100644 index 0d510f37428..00000000000 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java +++ /dev/null @@ -1,179 +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 static java.lang.Math.max; -import static java.lang.Math.min; -import static java.lang.Math.PI; - -import static org.apache.lucene.util.SloppyMath.asin; -import static org.apache.lucene.util.SloppyMath.cos; -import static org.apache.lucene.util.SloppyMath.TO_DEGREES; -import static org.apache.lucene.util.SloppyMath.TO_RADIANS; - -/** - * Basic reusable geo-spatial utility methods - * - * @lucene.experimental - */ -public final class GeoUtils { - /** Minimum longitude value. */ - public static final double MIN_LON_INCL = -180.0D; - - /** Maximum longitude value. */ - public static final double MAX_LON_INCL = 180.0D; - - /** Minimum latitude value. */ - public static final double MIN_LAT_INCL = -90.0D; - - /** Maximum latitude value. */ - public static final double MAX_LAT_INCL = 90.0D; - - /** min longitude value in radians */ - public static final double MIN_LON_RADIANS = TO_RADIANS * MIN_LON_INCL; - /** min latitude value in radians */ - public static final double MIN_LAT_RADIANS = TO_RADIANS * MIN_LAT_INCL; - /** max longitude value in radians */ - public static final double MAX_LON_RADIANS = TO_RADIANS * MAX_LON_INCL; - /** max latitude value in radians */ - public static final double MAX_LAT_RADIANS = TO_RADIANS * MAX_LAT_INCL; - - // WGS84 earth-ellipsoid parameters - /** mean earth axis in meters */ - // see http://earth-info.nga.mil/GandG/publications/tr8350.2/wgs84fin.pdf - public static final double EARTH_MEAN_RADIUS_METERS = 6_371_008.7714; - - // No instance: - private GeoUtils() { - } - - /** validates latitude value is within standard +/-90 coordinate bounds */ - public static void checkLatitude(double latitude) { - if (Double.isNaN(latitude) || latitude < MIN_LAT_INCL || latitude > MAX_LAT_INCL) { - throw new IllegalArgumentException("invalid latitude " + latitude + "; must be between " + MIN_LAT_INCL + " and " + MAX_LAT_INCL); - } - } - - /** validates longitude value is within standard +/-180 coordinate bounds */ - public static void checkLongitude(double longitude) { - if (Double.isNaN(longitude) || longitude < MIN_LON_INCL || longitude > MAX_LON_INCL) { - throw new IllegalArgumentException("invalid longitude " + longitude + "; must be between " + MIN_LON_INCL + " and " + MAX_LON_INCL); - } - } - - /** Compute Bounding Box for a circle using WGS-84 parameters */ - public static GeoRect circleToBBox(final double centerLat, final double centerLon, final double radiusMeters) { - final double radLat = TO_RADIANS * centerLat; - final double radLon = TO_RADIANS * centerLon; - // LUCENE-7143 - double radDistance = (radiusMeters + 7E-2) / EARTH_MEAN_RADIUS_METERS; - double minLat = radLat - radDistance; - double maxLat = radLat + radDistance; - double minLon; - double maxLon; - - if (minLat > MIN_LAT_RADIANS && maxLat < MAX_LAT_RADIANS) { - double deltaLon = asin(sloppySin(radDistance) / cos(radLat)); - minLon = radLon - deltaLon; - if (minLon < MIN_LON_RADIANS) { - minLon += 2d * PI; - } - maxLon = radLon + deltaLon; - if (maxLon > MAX_LON_RADIANS) { - maxLon -= 2d * PI; - } - } else { - // a pole is within the distance - minLat = max(minLat, MIN_LAT_RADIANS); - maxLat = min(maxLat, MAX_LAT_RADIANS); - minLon = MIN_LON_RADIANS; - maxLon = MAX_LON_RADIANS; - } - - return new GeoRect(TO_DEGREES * minLat, TO_DEGREES * maxLat, TO_DEGREES * minLon, TO_DEGREES * maxLon); - } - - // some sloppyish stuff, do we really need this to be done in a sloppy way? - // unless it is performance sensitive, we should try to remove. - private static final double PIO2 = Math.PI / 2D; - - /** - * Returns the trigonometric sine of an angle converted as a cos operation. - *

- * Note that this is not quite right... e.g. sin(0) != 0 - *

- * Special cases: - *

- * @param a an angle, in radians. - * @return the sine of the argument. - * @see Math#sin(double) - */ - // TODO: deprecate/remove this? at least its no longer public. - private static double sloppySin(double a) { - return cos(a - PIO2); - } - - /** maximum error from {@link #axisLat(double, double)}. logic must be prepared to handle this */ - public static final double AXISLAT_ERROR = 0.1D / EARTH_MEAN_RADIUS_METERS * TO_DEGREES; - - /** - * Calculate the latitude of a circle's intersections with its bbox meridians. - *

- * NOTE: the returned value will be +/- {@link #AXISLAT_ERROR} of the actual value. - * @param centerLat The latitude of the circle center - * @param radiusMeters The radius of the circle in meters - * @return A latitude - */ - public static double axisLat(double centerLat, double radiusMeters) { - // A spherical triangle with: - // r is the radius of the circle in radians - // l1 is the latitude of the circle center - // l2 is the latitude of the point at which the circle intersect's its bbox longitudes - // We know r is tangent to the bbox meridians at l2, therefore it is a right angle. - // So from the law of cosines, with the angle of l1 being 90, we have: - // cos(l1) = cos(r) * cos(l2) + sin(r) * sin(l2) * cos(90) - // The second part cancels out because cos(90) == 0, so we have: - // cos(l1) = cos(r) * cos(l2) - // Solving for l2, we get: - // l2 = acos( cos(l1) / cos(r) ) - // We ensure r is in the range (0, PI/2) and l1 in the range (0, PI/2]. This means we - // cannot divide by 0, and we will always get a positive value in the range [0, 1) as - // the argument to arc cosine, resulting in a range (0, PI/2]. - - double l1 = TO_RADIANS * centerLat; - double r = (radiusMeters + 7E-2) / EARTH_MEAN_RADIUS_METERS; - - // if we are within radius range of a pole, the lat is the pole itself - if (Math.abs(l1) + r >= MAX_LAT_RADIANS) { - return centerLat >= 0 ? MAX_LAT_INCL : MIN_LAT_INCL; - } - - // adjust l1 as distance from closest pole, to form a right triangle with bbox meridians - // and ensure it is in the range (0, PI/2] - l1 = centerLat >= 0 ? PIO2 - l1 : l1 + PIO2; - - double l2 = Math.acos(Math.cos(l1) / Math.cos(r)); - assert !Double.isNaN(l2); - - // now adjust back to range [-pi/2, pi/2], ie latitude in radians - l2 = centerLat >= 0 ? PIO2 - l2 : l2 - PIO2; - - return TO_DEGREES * l2; - } -} diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/Polygon.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/Polygon.java index 5dca23d3555..c0e23231de1 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/Polygon.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/Polygon.java @@ -18,6 +18,8 @@ package org.apache.lucene.spatial.util; import java.util.Arrays; +import org.apache.lucene.geo.GeoUtils; + /** * Represents a closed polygon on the earth's surface. * @lucene.experimental @@ -241,24 +243,6 @@ public final class Polygon { return holes.clone(); } - /** Returns the bounding box over an array of polygons */ - public static GeoRect getBoundingBox(Polygon[] polygons) { - // compute bounding box - double minLat = Double.POSITIVE_INFINITY; - double maxLat = Double.NEGATIVE_INFINITY; - double minLon = Double.POSITIVE_INFINITY; - double maxLon = Double.NEGATIVE_INFINITY; - - for (int i = 0;i < polygons.length; i++) { - minLat = Math.min(polygons[i].minLat, minLat); - maxLat = Math.max(polygons[i].maxLat, maxLat); - minLon = Math.min(polygons[i].minLon, minLon); - maxLon = Math.max(polygons[i].maxLon, maxLon); - } - - return new GeoRect(minLat, maxLat, minLon, maxLon); - } - /** Helper for multipolygon logic: returns true if any of the supplied polygons contain the point */ public static boolean contains(Polygon[] polygons, double latitude, double longitude) { for (Polygon polygon : polygons) { 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 2b5f885d949..da69b2452af 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 @@ -37,6 +37,7 @@ 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.geo.GeoUtils; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/GeoTestUtil.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/GeoTestUtil.java index 1785c12f445..1e4fcf37bce 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/GeoTestUtil.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/GeoTestUtil.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.util.SloppyMath; import com.carrotsearch.randomizedtesting.RandomizedContext; @@ -398,7 +399,7 @@ public class GeoTestUtil { double rectMinLongitude, double rectMaxLongitude, double centerLatitude, double centerLongitude, double radiusMeters) { - GeoRect box = GeoUtils.circleToBBox(centerLatitude, centerLongitude, radiusMeters); + GeoRect box = GeoRect.fromPointDistance(centerLatitude, centerLongitude, radiusMeters); System.out.println(""); System.out.println(""); System.out.println(" "); @@ -420,7 +421,7 @@ public class GeoTestUtil { System.out.println(" }).addTo(earth);"); plotLatApproximatelyOnEarthSurface("lat0", "#ffffff", 4.68, 0.0, 360.0); plotLatApproximatelyOnEarthSurface("lat1", "#ffffff", 180-93.09, 0.0, 360.0); - plotLatApproximatelyOnEarthSurface("axisLat", "#00ff00", GeoUtils.axisLat(centerLatitude, radiusMeters), box.minLon, box.maxLon); + plotLatApproximatelyOnEarthSurface("axisLat", "#00ff00", GeoRect.axisLat(centerLatitude, radiusMeters), box.minLon, box.maxLon); plotLonApproximatelyOnEarthSurface("axisLon", "#00ff00", centerLongitude, box.minLat, box.maxLat); System.out.println(" }"); System.out.println(" "); diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java index 7bd3fed44d2..06ef48ca378 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java @@ -18,6 +18,7 @@ package org.apache.lucene.spatial.util; import java.util.Locale; +import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.util.BytesRefBuilder; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.SloppyMath; @@ -155,7 +156,7 @@ public class TestGeoUtils extends LuceneTestCase { // TODO: randomly quantize radius too, to provoke exact math errors? - GeoRect bbox = GeoUtils.circleToBBox(centerLat, centerLon, radiusMeters); + GeoRect bbox = GeoRect.fromPointDistance(centerLat, centerLon, radiusMeters); int numPointsToTry = 1000; for(int i=0;i radius); } catch (AssertionError e) { GeoTestUtil.toWebGLEarth(latMin, latMax, lonMin, lonMax, centerLat, centerLon, radius); @@ -397,7 +398,7 @@ public class TestGeoUtils extends LuceneTestCase { } static boolean isDisjoint(double centerLat, double centerLon, double radius, double axisLat, double latMin, double latMax, double lonMin, double lonMax) { - if ((centerLon < lonMin || centerLon > lonMax) && (axisLat+GeoUtils.AXISLAT_ERROR < latMin || axisLat-GeoUtils.AXISLAT_ERROR > latMax)) { + if ((centerLon < lonMin || centerLon > lonMax) && (axisLat+GeoRect.AXISLAT_ERROR < latMin || axisLat-GeoRect.AXISLAT_ERROR > latMax)) { // circle not fully inside / crossing axis if (SloppyMath.haversinMeters(centerLat, centerLon, latMin, lonMin) > radius && SloppyMath.haversinMeters(centerLat, centerLon, latMin, lonMax) > radius &&