mirror of https://github.com/apache/lucene.git
LUCENE-7150: add Geo3DPoint.newDistance/Box/PolygonQuery
This commit is contained in:
parent
0b2040d61c
commit
bf0e59223d
|
@ -21,6 +21,9 @@ API Changes
|
|||
* LUCENE-7141: Switch OfflineSorter's ByteSequencesReader to
|
||||
BytesRefIterator (Mike McCandless)
|
||||
|
||||
* LUCENE-7150: Spatial3d gets useful APIs to create common shape
|
||||
queries, matching LatLonPoint. (Karl Wright via Mike McCandless)
|
||||
|
||||
Optimizations
|
||||
|
||||
* LUCENE-7071: Reduce bytes copying in OfflineSorter, giving ~10%
|
||||
|
|
|
@ -16,12 +16,19 @@
|
|||
*/
|
||||
package org.apache.lucene.spatial3d;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.FieldType;
|
||||
import org.apache.lucene.index.PointValues;
|
||||
import org.apache.lucene.spatial3d.geom.GeoPoint;
|
||||
import org.apache.lucene.spatial3d.geom.GeoShape;
|
||||
import org.apache.lucene.spatial3d.geom.PlanetModel;
|
||||
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
|
||||
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
|
||||
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
|
||||
import org.apache.lucene.spatial3d.geom.GeoPath;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
@ -39,6 +46,12 @@ import org.apache.lucene.util.NumericUtils;
|
|||
* @lucene.experimental */
|
||||
public final class Geo3DPoint extends Field {
|
||||
|
||||
/** Mean radius of the earth, in meters */
|
||||
protected final static double MEAN_EARTH_RADIUS_METERS = 6371008.7714;
|
||||
|
||||
/** How many radians are in one earth surface meter */
|
||||
protected final static double RADIANS_PER_METER = 1.0 / MEAN_EARTH_RADIUS_METERS;
|
||||
|
||||
/** Indexing {@link FieldType}. */
|
||||
public static final FieldType TYPE = new FieldType();
|
||||
static {
|
||||
|
@ -47,21 +60,135 @@ public final class Geo3DPoint extends Field {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a new Geo3DPoint field with the specified lat, lon (in radians).
|
||||
* Creates a new Geo3DPoint field with the specified latitude, longitude (in degrees).
|
||||
*
|
||||
* @throws IllegalArgumentException if the field name is null or lat or lon are out of bounds
|
||||
* @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds
|
||||
*/
|
||||
public Geo3DPoint(String name, double lat, double lon) {
|
||||
public Geo3DPoint(String name, double latitude, double longitude) {
|
||||
super(name, TYPE);
|
||||
// Translate lat/lon to x,y,z:
|
||||
final GeoPoint point = new GeoPoint(PlanetModel.WGS84, lat, lon);
|
||||
checkLatitude(latitude);
|
||||
checkLongitude(longitude);
|
||||
// Translate latitude/longitude to x,y,z:
|
||||
final GeoPoint point = new GeoPoint(PlanetModel.WGS84, fromDegrees(latitude), fromDegrees(longitude));
|
||||
fillFieldsData(point.x, point.y, point.z);
|
||||
}
|
||||
|
||||
/** Converts degress to radians */
|
||||
protected static double fromDegrees(final double degrees) {
|
||||
return Math.toRadians(degrees);
|
||||
}
|
||||
|
||||
/** Converts earth-surface meters to radians */
|
||||
protected static double fromMeters(final double meters) {
|
||||
return meters * RADIANS_PER_METER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query for matching points within the specified distance of the supplied location.
|
||||
* @param field field name. must not be null. Note that because
|
||||
* {@link PlanetModel#WGS84} is used, this query is approximate and may have up
|
||||
* to 0.5% error.
|
||||
*
|
||||
* @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
|
||||
* @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
|
||||
* @param radiusMeters maximum distance from the center in meters: must be non-negative and finite.
|
||||
* @return query matching points within this distance
|
||||
* @throws IllegalArgumentException if {@code field} is null, location has invalid coordinates, or radius is invalid.
|
||||
*/
|
||||
public static Query newDistanceQuery(final String field, final double latitude, final double longitude, final double radiusMeters) {
|
||||
checkLatitude(latitude);
|
||||
checkLongitude(longitude);
|
||||
final GeoShape shape = GeoCircleFactory.makeGeoCircle(PlanetModel.WGS84, fromDegrees(latitude), fromDegrees(longitude), fromMeters(radiusMeters));
|
||||
return newShapeQuery(field, shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query for matching a box.
|
||||
* <p>
|
||||
* The box may cross over the dateline.
|
||||
* @param field field name. must not be null.
|
||||
* @param minLatitude latitude lower bound: must be within standard +/-90 coordinate bounds.
|
||||
* @param maxLatitude latitude upper bound: must be within standard +/-90 coordinate bounds.
|
||||
* @param minLongitude longitude lower bound: must be within standard +/-180 coordinate bounds.
|
||||
* @param maxLongitude longitude upper bound: must be within standard +/-180 coordinate bounds.
|
||||
* @return query matching points within this box
|
||||
* @throws IllegalArgumentException if {@code field} is null, or the box has invalid coordinates.
|
||||
*/
|
||||
public static Query newBoxQuery(final String field, final double minLatitude, final double maxLatitude, final double minLongitude, final double maxLongitude) {
|
||||
checkLatitude(minLatitude);
|
||||
checkLongitude(minLongitude);
|
||||
checkLatitude(maxLatitude);
|
||||
checkLongitude(maxLongitude);
|
||||
final GeoShape shape = GeoBBoxFactory.makeGeoBBox(PlanetModel.WGS84,
|
||||
fromDegrees(maxLatitude), fromDegrees(minLatitude), fromDegrees(minLongitude), fromDegrees(maxLongitude));
|
||||
return newShapeQuery(field, shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query for matching a polygon.
|
||||
* <p>
|
||||
* The supplied {@code polyLatitudes}/{@code polyLongitudes} must be clockwise or counter-clockwise.
|
||||
* @param field field name. must not be null.
|
||||
* @param polyLatitudes latitude values for points of the polygon: must be within standard +/-90 coordinate bounds.
|
||||
* @param polyLongitudes longitude values for points of the polygon: must be within standard +/-180 coordinate bounds.
|
||||
* @return query matching points within this polygon
|
||||
*/
|
||||
public static Query newPolygonQuery(final String field, final double[] polyLatitudes, final double[] polyLongitudes) {
|
||||
if (polyLatitudes.length != polyLongitudes.length) {
|
||||
throw new IllegalArgumentException("same number of latitudes and longitudes required");
|
||||
}
|
||||
if (polyLatitudes.length < 4) {
|
||||
throw new IllegalArgumentException("need three or more points");
|
||||
}
|
||||
if (polyLatitudes[0] != polyLatitudes[polyLatitudes.length-1] || polyLongitudes[0] != polyLongitudes[polyLongitudes.length-1]) {
|
||||
throw new IllegalArgumentException("last point must equal first point");
|
||||
}
|
||||
|
||||
final List<GeoPoint> polyPoints = new ArrayList<>(polyLatitudes.length-1);
|
||||
for (int i = 0; i < polyLatitudes.length-1; i++) {
|
||||
final double latitude = polyLatitudes[i];
|
||||
final double longitude = polyLongitudes[i];
|
||||
checkLatitude(latitude);
|
||||
checkLongitude(longitude);
|
||||
polyPoints.add(new GeoPoint(PlanetModel.WGS84, fromDegrees(latitude), fromDegrees(longitude)));
|
||||
}
|
||||
// We don't know what the sense of the polygon is without providing the index of one vertex we know to be convex.
|
||||
// Since that doesn't fit with the "super-simple API" requirements, we just use the index of the first one, and people have to just
|
||||
// know to do it that way.
|
||||
final int convexPointIndex = 0;
|
||||
final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(PlanetModel.WGS84, polyPoints, convexPointIndex);
|
||||
return newShapeQuery(field, shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query for matching a path.
|
||||
* <p>
|
||||
* @param field field name. must not be null.
|
||||
* @param pathLatitudes latitude values for points of the path: must be within standard +/-90 coordinate bounds.
|
||||
* @param pathLongitudes longitude values for points of the path: must be within standard +/-180 coordinate bounds.
|
||||
* @param pathWidthMeters width of the path in meters.
|
||||
* @return query matching points within this polygon
|
||||
*/
|
||||
public static Query newPathQuery(final String field, final double[] pathLatitudes, final double[] pathLongitudes, final double pathWidthMeters) {
|
||||
if (pathLatitudes.length != pathLongitudes.length) {
|
||||
throw new IllegalArgumentException("same number of latitudes and longitudes required");
|
||||
}
|
||||
final GeoPoint[] points = new GeoPoint[pathLatitudes.length];
|
||||
for (int i = 0; i < pathLatitudes.length; i++) {
|
||||
final double latitude = pathLatitudes[i];
|
||||
final double longitude = pathLongitudes[i];
|
||||
checkLatitude(latitude);
|
||||
checkLongitude(longitude);
|
||||
points[i] = new GeoPoint(PlanetModel.WGS84, fromDegrees(latitude), fromDegrees(longitude));
|
||||
}
|
||||
final GeoShape shape = new GeoPath(PlanetModel.WGS84, fromMeters(pathWidthMeters), points);
|
||||
return newShapeQuery(field, shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Geo3DPoint field with the specified x,y,z.
|
||||
*
|
||||
* @throws IllegalArgumentException if the field name is null or lat or lon are out of bounds
|
||||
* @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds
|
||||
*/
|
||||
public Geo3DPoint(String name, double x, double y, double z) {
|
||||
super(name, TYPE);
|
||||
|
@ -112,4 +239,32 @@ public final class Geo3DPoint extends Field {
|
|||
result.append('>');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// TODO LUCENE-7152: share this with GeoUtils.java from spatial module
|
||||
|
||||
/** Minimum longitude value. */
|
||||
private static final double MIN_LON_INCL = -180.0D;
|
||||
|
||||
/** Maximum longitude value. */
|
||||
private static final double MAX_LON_INCL = 180.0D;
|
||||
|
||||
/** Minimum latitude value. */
|
||||
private static final double MIN_LAT_INCL = -90.0D;
|
||||
|
||||
/** Maximum latitude value. */
|
||||
private static final double MAX_LAT_INCL = 90.0D;
|
||||
|
||||
/** validates latitude value is within standard +/-90 coordinate bounds */
|
||||
private 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 */
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ public class TestGeo3DPoint extends LuceneTestCase {
|
|||
iwc.setCodec(getCodec());
|
||||
IndexWriter w = new IndexWriter(dir, iwc);
|
||||
Document doc = new Document();
|
||||
doc.add(new Geo3DPoint("field", toRadians(50.7345267), toRadians(-97.5303555)));
|
||||
doc.add(new Geo3DPoint("field", 50.7345267, -97.5303555));
|
||||
w.addDocument(doc);
|
||||
IndexReader r = DirectoryReader.open(w);
|
||||
// We can't wrap with "exotic" readers because the query must see the BKD3DDVFormat:
|
||||
|
@ -128,7 +128,7 @@ public class TestGeo3DPoint extends LuceneTestCase {
|
|||
}
|
||||
|
||||
private static double toRadians(double degrees) {
|
||||
return Math.PI*(degrees/360.0);
|
||||
return Math.PI*(degrees/180.0);
|
||||
}
|
||||
|
||||
private static PlanetModel getPlanetModel() {
|
||||
|
@ -508,13 +508,13 @@ public class TestGeo3DPoint extends LuceneTestCase {
|
|||
if (x == 0) {
|
||||
// Identical lat to old point
|
||||
lats[docID] = lats[oldDocID];
|
||||
lons[docID] = toRadians(randomLon());
|
||||
lons[docID] = randomLon();
|
||||
if (VERBOSE) {
|
||||
System.err.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID] + " (same lat as doc=" + oldDocID + ")");
|
||||
}
|
||||
} else if (x == 1) {
|
||||
// Identical lon to old point
|
||||
lats[docID] = toRadians(randomLat());
|
||||
lats[docID] = randomLat();
|
||||
lons[docID] = lons[oldDocID];
|
||||
if (VERBOSE) {
|
||||
System.err.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID] + " (same lon as doc=" + oldDocID + ")");
|
||||
|
@ -529,8 +529,8 @@ public class TestGeo3DPoint extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
lats[docID] = toRadians(randomLat());
|
||||
lons[docID] = toRadians(randomLon());
|
||||
lats[docID] = randomLat();
|
||||
lons[docID] = randomLon();
|
||||
haveRealDoc = true;
|
||||
if (VERBOSE) {
|
||||
System.err.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID]);
|
||||
|
@ -759,7 +759,7 @@ public class TestGeo3DPoint extends LuceneTestCase {
|
|||
if (Double.isNaN(lats[id]) == false) {
|
||||
|
||||
// Accurate point:
|
||||
GeoPoint point1 = new GeoPoint(PlanetModel.WGS84, lats[id], lons[id]);
|
||||
GeoPoint point1 = new GeoPoint(PlanetModel.WGS84, toRadians(lats[id]), toRadians(lons[id]));
|
||||
|
||||
// Quantized point (32 bits per dim):
|
||||
GeoPoint point2 = quantize(PlanetModel.WGS84.getMaximumMagnitude(), point1);
|
||||
|
@ -795,12 +795,12 @@ public class TestGeo3DPoint extends LuceneTestCase {
|
|||
}
|
||||
|
||||
public void testToString() {
|
||||
Geo3DPoint point = new Geo3DPoint("point", toRadians(44.244272), toRadians(7.769736));
|
||||
assertEquals("Geo3DPoint <point: x=0.9248467864160119 y=0.06280434265368656 z=0.37682349005486243>", point.toString());
|
||||
Geo3DPoint point = new Geo3DPoint("point", 44.244272, 7.769736);
|
||||
assertEquals("Geo3DPoint <point: x=0.709426287693908 y=0.09679758561541502 z=0.6973564369288621>", point.toString());
|
||||
}
|
||||
|
||||
public void testShapeQueryToString() {
|
||||
assertEquals("PointInGeo3DShapeQuery: field=point: Shape: GeoStandardCircle: {planetmodel=PlanetModel.WGS84, center=[lat=0.3861041107739683, lon=0.06780373760536706], radius=0.1(5.729577951308232)}",
|
||||
assertEquals("PointInGeo3DShapeQuery: field=point: Shape: GeoStandardCircle: {planetmodel=PlanetModel.WGS84, center=[lat=0.7722082215479366, lon=0.13560747521073413], radius=0.1(5.729577951308232)}",
|
||||
Geo3DPoint.newShapeQuery("point", GeoCircleFactory.makeGeoCircle(PlanetModel.WGS84, toRadians(44.244272), toRadians(7.769736), 0.1)).toString());
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue