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());
   }