mirror of https://github.com/apache/lucene.git
LUCENE-7152: Refactor GeoUtils from lucene-spatial to core module.
This commit is contained in:
parent
461c9b4fef
commit
637dce83e2
|
@ -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)
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>");
|
||||
|
|
|
@ -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 &&
|
||||
|
|
Loading…
Reference in New Issue