LUCENE-7152: Refactor GeoUtils from lucene-spatial to core module.

This commit is contained in:
nknize 2016-04-01 11:03:11 -05:00
parent 461c9b4fef
commit 637dce83e2
21 changed files with 280 additions and 234 deletions

View File

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

View File

@ -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.
* <p>
* Note that this is not quite right... e.g. sin(0) != 0
* <p>
* Special cases:
* <ul>
* <li>If the argument is {@code NaN} or an infinity, then the result is {@code NaN}.
* </ul>
* @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);
}
}

View File

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

View File

@ -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;
/**

View File

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

View File

@ -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 &&

View File

@ -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];

View File

@ -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.

View File

@ -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;
/**
* <p>

View File

@ -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,

View File

@ -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 &&

View File

@ -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

View File

@ -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

View File

@ -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;
/**

View File

@ -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

View File

@ -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.
* <p>
* <b>NOTE:</b> 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);
}
}

View File

@ -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.
* <p>
* Note that this is not quite right... e.g. sin(0) != 0
* <p>
* Special cases:
* <ul>
* <li>If the argument is {@code NaN} or an infinity, then the result is {@code NaN}.
* </ul>
* @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.
* <p>
* <b>NOTE:</b> 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;
}
}

View File

@ -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) {

View File

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

View File

@ -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("<!DOCTYPE HTML>");
System.out.println("<html>");
System.out.println(" <head>");
@ -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(" </script>");

View File

@ -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<numPointsToTry;i++) {
@ -218,7 +219,7 @@ public class TestGeoUtils extends LuceneTestCase {
double lat = GeoTestUtil.nextLatitude();
double lon = GeoTestUtil.nextLongitude();
double radius = 50000000 * random().nextDouble();
GeoRect box = GeoUtils.circleToBBox(lat, lon, radius);
GeoRect box = GeoRect.fromPointDistance(lat, lon, radius);
final GeoRect box1;
final GeoRect box2;
if (box.crossesDateline()) {
@ -247,7 +248,7 @@ public class TestGeoUtils extends LuceneTestCase {
double lat = GeoTestUtil.nextLatitude();
double lon = GeoTestUtil.nextLongitude();
double radius = 50000000 * random().nextDouble();
GeoRect box = GeoUtils.circleToBBox(lat, lon, radius);
GeoRect box = GeoRect.fromPointDistance(lat, lon, radius);
if (box.maxLon - lon < 90 && lon - box.minLon < 90) {
double minPartialDistance = Math.max(SloppyMath.haversinSortKey(lat, lon, lat, box.maxLon),
@ -270,7 +271,7 @@ public class TestGeoUtils extends LuceneTestCase {
for (int i = 0; i < 1000; i++) {
double centerLat = GeoTestUtil.nextLatitude();
double centerLon = GeoTestUtil.nextLongitude();
GeoRect rect = GeoUtils.circleToBBox(centerLat, centerLon, Double.POSITIVE_INFINITY);
GeoRect rect = GeoRect.fromPointDistance(centerLat, centerLon, Double.POSITIVE_INFINITY);
assertEquals(-180.0, rect.minLon, 0.0D);
assertEquals(180.0, rect.maxLon, 0.0D);
assertEquals(-90.0, rect.minLat, 0.0D);
@ -281,16 +282,16 @@ public class TestGeoUtils extends LuceneTestCase {
public void testAxisLat() {
double earthCircumference = 2D * Math.PI * GeoUtils.EARTH_MEAN_RADIUS_METERS;
assertEquals(90, GeoUtils.axisLat(0, earthCircumference / 4), 0.0D);
assertEquals(90, GeoRect.axisLat(0, earthCircumference / 4), 0.0D);
for (int i = 0; i < 100; ++i) {
boolean reallyBig = random().nextInt(10) == 0;
final double maxRadius = reallyBig ? 1.1 * earthCircumference : earthCircumference / 8;
final double radius = maxRadius * random().nextDouble();
double prevAxisLat = GeoUtils.axisLat(0.0D, radius);
double prevAxisLat = GeoRect.axisLat(0.0D, radius);
for (double lat = 0.1D; lat < 90D; lat += 0.1D) {
double nextAxisLat = GeoUtils.axisLat(lat, radius);
GeoRect bbox = GeoUtils.circleToBBox(lat, 180D, radius);
double nextAxisLat = GeoRect.axisLat(lat, radius);
GeoRect bbox = GeoRect.fromPointDistance(lat, 180D, radius);
double dist = SloppyMath.haversinMeters(lat, 180D, nextAxisLat, bbox.maxLon);
if (nextAxisLat < GeoUtils.MAX_LAT_INCL) {
assertEquals("lat = " + lat, dist, radius, 0.1D);
@ -299,10 +300,10 @@ public class TestGeoUtils extends LuceneTestCase {
prevAxisLat = nextAxisLat;
}
prevAxisLat = GeoUtils.axisLat(-0.0D, radius);
prevAxisLat = GeoRect.axisLat(-0.0D, radius);
for (double lat = -0.1D; lat > -90D; lat -= 0.1D) {
double nextAxisLat = GeoUtils.axisLat(lat, radius);
GeoRect bbox = GeoUtils.circleToBBox(lat, 180D, radius);
double nextAxisLat = GeoRect.axisLat(lat, radius);
GeoRect bbox = GeoRect.fromPointDistance(lat, 180D, radius);
double dist = SloppyMath.haversinMeters(lat, 180D, nextAxisLat, bbox.maxLon);
if (nextAxisLat > GeoUtils.MIN_LAT_INCL) {
assertEquals("lat = " + lat, dist, radius, 0.1D);
@ -321,13 +322,13 @@ public class TestGeoUtils extends LuceneTestCase {
final double centerLat = -90 + 180.0 * random().nextDouble();
final double centerLon = -180 + 360.0 * random().nextDouble();
final double radius = 50_000_000D * random().nextDouble();
final GeoRect box = GeoUtils.circleToBBox(centerLat, centerLon, radius);
final GeoRect box = GeoRect.fromPointDistance(centerLat, centerLon, radius);
// TODO: remove this leniency!
if (box.crossesDateline()) {
--i; // try again...
continue;
}
final double axisLat = GeoUtils.axisLat(centerLat, radius);
final double axisLat = GeoRect.axisLat(centerLat, radius);
for (int k = 0; k < 1000; ++k) {
@ -380,7 +381,7 @@ public class TestGeoUtils extends LuceneTestCase {
"lonMax=%s) == false BUT\n" +
"haversin(%s, %s, %s, %s) = %s\nbbox=%s",
centerLat, centerLon, radius, latMin, latMax, lonMin, lonMax,
centerLat, centerLon, lat, lon, distance, GeoUtils.circleToBBox(centerLat, centerLon, radius)),
centerLat, centerLon, lat, lon, distance, GeoRect.fromPointDistance(centerLat, centerLon, radius)),
distance > 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 &&