You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mi...@apache.org on 2016/03/31 17:52:14 UTC
lucene-solr:master: LUCENE-7150: add
Geo3DPoint.newDistance/Box/PolygonQuery
Repository: lucene-solr
Updated Branches:
refs/heads/master 0b2040d61 -> bf0e59223
LUCENE-7150: add Geo3DPoint.newDistance/Box/PolygonQuery
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/bf0e5922
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/bf0e5922
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/bf0e5922
Branch: refs/heads/master
Commit: bf0e59223d0fdf6de28f2b8a495331222e3232c8
Parents: 0b2040d
Author: Mike McCandless <mi...@apache.org>
Authored: Thu Mar 31 11:53:57 2016 -0400
Committer: Mike McCandless <mi...@apache.org>
Committed: Thu Mar 31 11:54:01 2016 -0400
----------------------------------------------------------------------
lucene/CHANGES.txt | 3 +
.../org/apache/lucene/spatial3d/Geo3DPoint.java | 167 ++++++++++++++++++-
.../apache/lucene/spatial3d/TestGeo3DPoint.java | 20 +--
3 files changed, 174 insertions(+), 16 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bf0e5922/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index e86f44d..dd71fa7 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -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%
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bf0e5922/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java
index 955a2bc..6b34518 100644
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java
@@ -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);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bf0e5922/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java b/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
index 5c0044f..ccaf093 100644
--- a/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
+++ b/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
@@ -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());
}