You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by iv...@apache.org on 2020/02/14 10:40:35 UTC

[lucene-solr] branch branch_8x updated: LUCENE-9218: XYGeometries should expose values as floats (#1252)

This is an automated email from the ASF dual-hosted git repository.

ivera pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/branch_8x by this push:
     new ca3319c  LUCENE-9218: XYGeometries should expose values as floats (#1252)
ca3319c is described below

commit ca3319cdbce14120291b1dfcda94d25abd677276
Author: Ignacio Vera <iv...@apache.org>
AuthorDate: Fri Feb 14 11:39:10 2020 +0100

    LUCENE-9218: XYGeometries should expose values as floats (#1252)
---
 lucene/CHANGES.txt                                 |   2 +
 .../src/java/org/apache/lucene/geo/Line2D.java     |   2 +-
 .../src/java/org/apache/lucene/geo/Polygon2D.java  |   4 +-
 .../java/org/apache/lucene/geo/Tessellator.java    |  14 +--
 .../org/apache/lucene/geo/XYEncodingUtils.java     |  26 +++--
 .../src/java/org/apache/lucene/geo/XYLine.java     |  50 ++++------
 .../src/java/org/apache/lucene/geo/XYPoint.java    |  18 ++--
 .../src/java/org/apache/lucene/geo/XYPolygon.java  |  54 ++++-------
 .../java/org/apache/lucene/geo/XYRectangle.java    |  44 +++++----
 .../java/org/apache/lucene/geo/XYRectangle2D.java  |   5 +-
 .../lucene/document/BaseXYShapeTestCase.java       |  18 ++--
 .../lucene/document/TestXYLineShapeQueries.java    |  10 +-
 .../document/TestXYMultiLineShapeQueries.java      |   2 +-
 .../document/TestXYMultiPointShapeQueries.java     |   2 +-
 .../document/TestXYMultiPolygonShapeQueries.java   |   2 +-
 .../lucene/document/TestXYPointShapeQueries.java   |   6 +-
 .../lucene/document/TestXYPolygonShapeQueries.java |   2 +-
 .../org/apache/lucene/document/TestXYShape.java    |  34 +++----
 .../lucene/document/TestXYShapeEncoding.java       |   8 +-
 .../test/org/apache/lucene/geo/ShapeTestUtil.java  |  77 ++++++++-------
 .../src/test/org/apache/lucene/geo/TestXYLine.java |  99 +++++++++++++++++++
 .../test/org/apache/lucene/geo/TestXYPoint.java    |  77 +++++++++++++++
 .../test/org/apache/lucene/geo/TestXYPolygon.java  | 107 +++++++++++++++++++++
 .../org/apache/lucene/geo/TestXYRectangle.java     | 106 ++++++++++++++++++++
 .../org/apache/lucene/geo/TestXYRectangle2D.java   |  16 +--
 25 files changed, 589 insertions(+), 196 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 46e31bf..d507284 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -22,6 +22,8 @@ API Changes
 
 * LUCENE-8621: Refactor LatLonShape, XYShape, and all query and utility classes to core. (Nick Knize)
 
+* LUCENE-9218: XY geometries API works in float space. (Ignacio Vera)
+
 New Features
 ---------------------
 
diff --git a/lucene/core/src/java/org/apache/lucene/geo/Line2D.java b/lucene/core/src/java/org/apache/lucene/geo/Line2D.java
index 5c6593a..1d82621 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Line2D.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Line2D.java
@@ -50,7 +50,7 @@ final class Line2D implements Component2D {
     this.maxY = line.maxY;
     this.minX = line.minX;
     this.maxX = line.maxX;
-    this.tree = EdgeTree.createTree(line.getX(), line.getY());
+    this.tree = EdgeTree.createTree(XYEncodingUtils.floatArrayToDoubleArray(line.getX()), XYEncodingUtils.floatArrayToDoubleArray(line.getY()));
   }
 
   @Override
diff --git a/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java b/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java
index 3e5fdb0..b69b66c 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java
@@ -49,10 +49,10 @@ final class Polygon2D implements Component2D {
   }
 
   private Polygon2D(XYPolygon polygon, Component2D holes) {
-    this(polygon.minX, polygon.maxX, polygon.minY, polygon.maxY, polygon.getPolyX(), polygon.getPolyY(), holes);
+    this(polygon.minX, polygon.maxX, polygon.minY, polygon.maxY, XYEncodingUtils.floatArrayToDoubleArray(polygon.getPolyX()), XYEncodingUtils.floatArrayToDoubleArray(polygon.getPolyY()), holes);
   }
 
-  protected Polygon2D(Polygon polygon, Component2D holes) {
+  private Polygon2D(Polygon polygon, Component2D holes) {
     this(polygon.minLon, polygon.maxLon, polygon.minLat, polygon.maxLat, polygon.getPolyLons(), polygon.getPolyLats(), holes);
   }
 
diff --git a/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java b/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java
index 12695d8..061c50a 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java
@@ -122,9 +122,9 @@ final public class Tessellator {
 
   public static final List<Triangle> tessellate(final XYPolygon polygon) {
     // Attempt to establish a doubly-linked list of the provided shell points (should be CCW, but this will correct);
-    // then filter instances of intersections.
-    Node outerNode = createDoublyLinkedList(polygon.getPolyX(), polygon.getPolyY(), polygon.getWindingOrder(), false,
-        0, WindingOrder.CW);
+    // then filter instances of intersections.0
+    Node outerNode = createDoublyLinkedList(XYEncodingUtils.floatArrayToDoubleArray(polygon.getPolyX()), XYEncodingUtils.floatArrayToDoubleArray(polygon.getPolyY()),
+        polygon.getWindingOrder(), false, 0, WindingOrder.CW);
     // If an outer node hasn't been detected, the shape is malformed. (must comply with OGC SFA specification)
     if(outerNode == null) {
       throw new IllegalArgumentException("Malformed shape detected in Tessellator!");
@@ -193,7 +193,8 @@ final public class Tessellator {
     int nodeIndex = polygon.numPoints() ;
     for(int i = 0; i < polygon.numHoles(); ++i) {
       // create the doubly-linked hole list
-      Node list = createDoublyLinkedList(holes[i].getPolyX(), holes[i].getPolyY(), holes[i].getWindingOrder(), false, nodeIndex, WindingOrder.CCW);
+      Node list = createDoublyLinkedList(XYEncodingUtils.floatArrayToDoubleArray(holes[i].getPolyX()),
+          XYEncodingUtils.floatArrayToDoubleArray(holes[i].getPolyY()), holes[i].getWindingOrder(), false, nodeIndex, WindingOrder.CCW);
       // Determine if the resulting hole polygon was successful.
       if(list != null) {
         // Add the leftmost vertex of the hole.
@@ -1059,8 +1060,9 @@ final public class Tessellator {
       this.vrtxIdx = vertexIndex;
       this.polyX = x;
       this.polyY = y;
-      this.y = isGeo ? encodeLatitude(polyY[vrtxIdx]) : XYEncodingUtils.encode(polyY[vrtxIdx]);
-      this.x = isGeo ? encodeLongitude(polyX[vrtxIdx]) : XYEncodingUtils.encode(polyX[vrtxIdx]);
+      // casting to float is safe as original values for non-geo are represented as floats
+      this.y = isGeo ? encodeLatitude(polyY[vrtxIdx]) : XYEncodingUtils.encode((float) polyY[vrtxIdx]);
+      this.x = isGeo ? encodeLongitude(polyX[vrtxIdx]) : XYEncodingUtils.encode((float) polyX[vrtxIdx]);
       this.morton = BitUtil.interleave(this.x ^ 0x80000000, this.y ^ 0x80000000);
       this.previous = null;
       this.next = null;
diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYEncodingUtils.java b/lucene/core/src/java/org/apache/lucene/geo/XYEncodingUtils.java
index b2c1d01..4f2a64f 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/XYEncodingUtils.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/XYEncodingUtils.java
@@ -33,11 +33,12 @@ public final class XYEncodingUtils {
   private XYEncodingUtils() {
   }
 
-  /** validates value is within +/-{@link Float#MAX_VALUE} coordinate bounds */
-  public static void checkVal(double x) {
-    if (Double.isNaN(x) || x < MIN_VAL_INCL || x > MAX_VAL_INCL) {
+  /** validates value is a number and finite */
+  static float checkVal(float x) {
+    if (Float.isFinite(x) == false) {
       throw new IllegalArgumentException("invalid value " + x + "; must be between " + MIN_VAL_INCL + " and " + MAX_VAL_INCL);
     }
+    return x;
   }
 
   /**
@@ -46,9 +47,8 @@ public final class XYEncodingUtils {
    * @return encoded value as a 32-bit {@code int}
    * @throws IllegalArgumentException if value is out of bounds
    */
-  public static int encode(double x) {
-    checkVal(x);
-    return NumericUtils.floatToSortableInt((float)x);
+  public static int encode(float x) {
+    return NumericUtils.floatToSortableInt(checkVal(x));
   }
 
   /**
@@ -56,8 +56,8 @@ public final class XYEncodingUtils {
    * @param encoded encoded value: 32-bit quantized value.
    * @return decoded value value.
    */
-  public static double decode(int encoded) {
-    double result = NumericUtils.sortableIntToFloat(encoded);
+  public static float decode(int encoded) {
+    float result = NumericUtils.sortableIntToFloat(encoded);
     assert result >=  MIN_VAL_INCL && result <= MAX_VAL_INCL;
     return result;
   }
@@ -68,7 +68,15 @@ public final class XYEncodingUtils {
    * @param offset offset into {@code src} to decode from.
    * @return decoded value.
    */
-  public static double decode(byte[] src, int offset) {
+  public static float decode(byte[] src, int offset) {
     return decode(NumericUtils.sortableBytesToInt(src, offset));
   }
+
+  static double[] floatArrayToDoubleArray(float[] f) {
+    double[] d = new double[f.length];
+    for (int i = 0; i < f.length; i++) {
+      d[i] = f[i];
+    }
+    return d;
+  }
 }
diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYLine.java b/lucene/core/src/java/org/apache/lucene/geo/XYLine.java
index c3f59ac..f2e446a 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/XYLine.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/XYLine.java
@@ -18,24 +18,26 @@ package org.apache.lucene.geo;
 
 import java.util.Arrays;
 
+import static org.apache.lucene.geo.XYEncodingUtils.checkVal;
+
 /**
  * Represents a line in cartesian space. You can construct the Line directly with {@code float[]}, {@code float[]} x, y arrays
  * coordinates.
  */
 public class XYLine extends XYGeometry {
   /** array of x coordinates */
-  private final double[] x;
+  private final float[] x;
   /** array of y coordinates */
-  private final double[] y;
+  private final float[] y;
 
   /** minimum x of this line's bounding box */
-  public final double minX;
+  public final float minX;
   /** maximum y of this line's bounding box */
-  public final double maxX;
+  public final float maxX;
   /** minimum y of this line's bounding box */
-  public final double minY;
+  public final float minY;
   /** maximum y of this line's bounding box */
-  public final double maxY;
+  public final float maxY;
 
   /**
    * Creates a new Line from the supplied X/Y array.
@@ -55,23 +57,19 @@ public class XYLine extends XYGeometry {
     }
 
     // compute bounding box
-    double minX = x[0];
-    double minY = y[0];
-    double maxX = x[0];
-    double maxY = y[0];
+    float minX = Float.MAX_VALUE;
+    float minY = Float.MAX_VALUE;
+    float maxX = -Float.MAX_VALUE;
+    float maxY = -Float.MAX_VALUE;
     for (int i = 0; i < x.length; ++i) {
-      minX = Math.min(x[i], minX);
-      minY = Math.min(y[i], minY);
+      minX = Math.min(checkVal(x[i]), minX);
+      minY = Math.min(checkVal(y[i]), minY);
       maxX = Math.max(x[i], maxX);
       maxY = Math.max(y[i], maxY);
     }
+    this.x = x.clone();
+    this.y = y.clone();
 
-    this.x = new double[x.length];
-    this.y = new double[y.length];
-    for (int i = 0; i < x.length; ++i) {
-      this.x[i] = (double)x[i];
-      this.y[i] = (double)y[i];
-    }
     this.minX = minX;
     this.maxX = maxX;
     this.minY = minY;
@@ -84,22 +82,22 @@ public class XYLine extends XYGeometry {
   }
 
   /** Returns x value at given index */
-  public double getX(int vertex) {
+  public float getX(int vertex) {
     return x[vertex];
   }
 
   /** Returns y value at given index */
-  public double getY(int vertex) {
+  public float getY(int vertex) {
     return y[vertex];
   }
 
   /** Returns a copy of the internal x array */
-  public double[] getX() {
+  public float[] getX() {
     return x.clone();
   }
 
   /** Returns a copy of the internal y array */
-  public double[] getY() {
+  public float[] getY() {
     return y.clone();
   }
 
@@ -108,14 +106,6 @@ public class XYLine extends XYGeometry {
     return Line2D.create(this);
   }
 
-  public String toGeoJSON() {
-    StringBuilder sb = new StringBuilder();
-    sb.append("[");
-    sb.append(Polygon.verticesToGeoJSON(x, y));
-    sb.append("]");
-    return sb.toString();
-  }
-
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYPoint.java b/lucene/core/src/java/org/apache/lucene/geo/XYPoint.java
index 9b6d0ee..ad33133 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/XYPoint.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/XYPoint.java
@@ -17,6 +17,8 @@
 
 package org.apache.lucene.geo;
 
+import static org.apache.lucene.geo.XYEncodingUtils.checkVal;
+
 /**
  * Represents a point on the earth's surface.  You can construct the point directly with {@code double}
  * coordinates.
@@ -30,25 +32,25 @@ package org.apache.lucene.geo;
 public final class XYPoint extends XYGeometry {
 
   /** latitude coordinate */
-  private final double x;
+  private final float x;
   /** longitude coordinate */
-  private final double y;
+  private final float y;
 
   /**
    * Creates a new Point from the supplied latitude/longitude.
    */
   public XYPoint(float x, float y) {
-    this.x = x;
-    this.y = y;
+    this.x = checkVal(x);
+    this.y = checkVal(y);
   }
 
   /** Returns latitude value at given index */
-  public double getX() {
+  public float getX() {
     return x;
   }
 
   /** Returns longitude value at given index */
-  public double getY() {
+  public float getY() {
     return y;
   }
 
@@ -67,8 +69,8 @@ public final class XYPoint extends XYGeometry {
 
   @Override
   public int hashCode() {
-    int result = Double.hashCode(x);
-    result = 31 * result + Double.hashCode(y);
+    int result = Float.hashCode(x);
+    result = 31 * result + Float.hashCode(y);
     return result;
   }
 
diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYPolygon.java b/lucene/core/src/java/org/apache/lucene/geo/XYPolygon.java
index 00ec377..9ed5592 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/XYPolygon.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/XYPolygon.java
@@ -18,23 +18,25 @@ package org.apache.lucene.geo;
 
 import java.util.Arrays;
 
+import static org.apache.lucene.geo.XYEncodingUtils.checkVal;
+
 /**
  * Represents a polygon in cartesian space. You can construct the Polygon directly with {@code float[]}, {@code float[]} x, y arrays
  * coordinates.
  */
 public final class XYPolygon extends XYGeometry {
-  private final double[] x;
-  private final double[] y;
+  private final float[] x;
+  private final float[] y;
   private final XYPolygon[] holes;
 
   /** minimum x of this polygon's bounding box area */
-  public final double minX;
+  public final float minX;
   /** maximum x of this polygon's bounding box area */
-  public final double maxX;
+  public final float maxX;
   /** minimum y of this polygon's bounding box area */
-  public final double minY;
+  public final float minY;
   /** maximum y of this polygon's bounding box area */
-  public final double maxY;
+  public final float maxY;
   /** winding order of the vertices */
   private final GeoUtils.WindingOrder windingOrder;
 
@@ -69,26 +71,22 @@ public final class XYPolygon extends XYGeometry {
         throw new IllegalArgumentException("holes may not contain holes: polygons may not nest.");
       }
     }
-    this.x = new double[x.length];
-    this.y = new double[y.length];
-    for (int i = 0; i < x.length; ++i) {
-      this.x[i] = (double)x[i];
-      this.y[i] = (double)y[i];
-    }
+    this.x = x.clone();
+    this.y = y.clone();
     this.holes = holes.clone();
 
     // compute bounding box
-    double minX = x[0];
-    double maxX = x[0];
-    double minY = y[0];
-    double maxY = y[0];
+    float minX = checkVal(x[0]);
+    float maxX = x[0];
+    float minY = checkVal(y[0]);
+    float maxY = y[0];
 
     double windingSum = 0d;
     final int numPts = x.length - 1;
     for (int i = 1, j = 0; i < numPts; j = i++) {
-      minX = Math.min(x[i], minX);
+      minX = Math.min(checkVal(x[i]), minX);
       maxX = Math.max(x[i], maxX);
-      minY = Math.min(y[i], minY);
+      minY = Math.min(checkVal(y[i]), minY);
       maxY = Math.max(y[i], maxY);
       // compute signed area
       windingSum += (x[j] - x[numPts])*(y[i] - y[numPts])
@@ -107,22 +105,22 @@ public final class XYPolygon extends XYGeometry {
   }
 
   /** Returns a copy of the internal x array */
-  public double[] getPolyX() {
+  public float[] getPolyX() {
     return x.clone();
   }
 
   /** Returns x value at given index */
-  public double getPolyX(int vertex) {
+  public float getPolyX(int vertex) {
     return x[vertex];
   }
 
   /** Returns a copy of the internal y array */
-  public double[] getPolyY() {
+  public float[] getPolyY() {
     return y.clone();
   }
 
   /** Returns y value at given index */
-  public double getPolyY(int vertex) {
+  public float getPolyY(int vertex) {
     return y[vertex];
   }
 
@@ -150,18 +148,6 @@ public final class XYPolygon extends XYGeometry {
     return Polygon2D.create(this);
   }
 
-  public String toGeoJSON() {
-    StringBuilder sb = new StringBuilder();
-    sb.append("[");
-    sb.append(Polygon.verticesToGeoJSON(y, x));
-    for (XYPolygon hole : holes) {
-      sb.append(",");
-      sb.append(Polygon.verticesToGeoJSON(hole.y, hole.x));
-    }
-    sb.append("]");
-    return sb.toString();
-  }
-
   @Override
   public int hashCode() {
     final int prime = 31;
diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYRectangle.java b/lucene/core/src/java/org/apache/lucene/geo/XYRectangle.java
index 23175b1..8012d79 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/XYRectangle.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/XYRectangle.java
@@ -16,25 +16,31 @@
  */
 package org.apache.lucene.geo;
 
+import static org.apache.lucene.geo.XYEncodingUtils.checkVal;
+
 /** Represents a x/y cartesian rectangle. */
 public final class XYRectangle extends XYGeometry {
   /** minimum x value */
-  public final double minX;
+  public final float minX;
   /** minimum y value */
-  public final double maxX;
+  public final float maxX;
   /** maximum x value */
-  public final double minY;
+  public final float minY;
   /** maximum y value */
-  public final double maxY;
+  public final float maxY;
 
   /** Constructs a bounding box by first validating the provided x and y coordinates */
-  public XYRectangle(double minX, double maxX, double minY, double maxY) {
-    this.minX = minX;
-    this.maxX = maxX;
-    this.minY = minY;
-    this.maxY = maxY;
-    assert minX <= maxX;
-    assert minY <= maxY;
+  public XYRectangle(float minX, float maxX, float minY, float maxY) {
+    if (minX > maxX) {
+      throw new IllegalArgumentException("minX must be lower than maxX, got " + minX + " > " + maxX);
+    }
+    if (minY > maxY) {
+      throw new IllegalArgumentException("minY must be lower than maxY, got " + minY + " > " + maxY);
+    }
+    this.minX = checkVal(minX);
+    this.maxX = checkVal(maxX);
+    this.minY = checkVal(minY);
+    this.maxY = checkVal(maxY);
   }
 
   @Override
@@ -49,10 +55,10 @@ public final class XYRectangle extends XYGeometry {
 
     XYRectangle rectangle = (XYRectangle) o;
 
-    if (Double.compare(rectangle.minX, minX) != 0) return false;
-    if (Double.compare(rectangle.minY, minY) != 0) return false;
-    if (Double.compare(rectangle.maxX, maxX) != 0) return false;
-    return Double.compare(rectangle.maxY, maxY) == 0;
+    if (Float.compare(rectangle.minX, minX) != 0) return false;
+    if (Float.compare(rectangle.minY, minY) != 0) return false;
+    if (Float.compare(rectangle.maxX, maxX) != 0) return false;
+    return Float.compare(rectangle.maxY, maxY) == 0;
 
   }
 
@@ -60,13 +66,13 @@ public final class XYRectangle extends XYGeometry {
   public int hashCode() {
     int result;
     long temp;
-    temp = Double.doubleToLongBits(minX);
+    temp = Float.floatToIntBits(minX);
     result = (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(minY);
+    temp = Float.floatToIntBits(minY);
     result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxX);
+    temp = Float.floatToIntBits(maxX);
     result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxY);
+    temp = Float.floatToIntBits(maxY);
     result = 31 * result + (int) (temp ^ (temp >>> 32));
     return result;
   }
diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYRectangle2D.java b/lucene/core/src/java/org/apache/lucene/geo/XYRectangle2D.java
index b15d797..3267d44 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/XYRectangle2D.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/XYRectangle2D.java
@@ -235,9 +235,6 @@ final class XYRectangle2D implements Component2D {
 
   /** create a component2D from the provided XY rectangle */
   static Component2D create(XYRectangle rectangle) {
-    return new XYRectangle2D(XYEncodingUtils.decode(XYEncodingUtils.encode(rectangle.minX)),
-        XYEncodingUtils.decode(XYEncodingUtils.encode(rectangle.maxX)),
-        XYEncodingUtils.decode(XYEncodingUtils.encode(rectangle.minY)),
-        XYEncodingUtils.decode(XYEncodingUtils.encode(rectangle.maxY)));
+    return new XYRectangle2D(rectangle.minX, rectangle.maxX, rectangle.minY, rectangle.maxY);
   }
 }
\ No newline at end of file
diff --git a/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java b/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
index 40bca2b..ef34cfe 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
@@ -126,8 +126,8 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
     float[] x = new float[poly.numPoints() - 1];
     float[] y = new float[x.length];
     for (int i = 0; i < x.length; ++i) {
-      x[i] = (float) poly.getPolyX(i);
-      y[i] = (float) poly.getPolyY(i);
+      x[i] = poly.getPolyX(i);
+      y[i] = poly.getPolyY(i);
     }
 
     return new XYLine(x, y);
@@ -144,8 +144,8 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
     int numPoints = TestUtil.nextInt(random, 1, 20);
     float[][] points = new float[numPoints][2];
     for (int i = 0; i < numPoints; i++) {
-      points[i][0] = (float) ShapeTestUtil.nextDouble(random);
-      points[i][1] = (float) ShapeTestUtil.nextDouble(random);
+      points[i][0] =  ShapeTestUtil.nextFloat(random);
+      points[i][1] =  ShapeTestUtil.nextFloat(random);
     }
     return points;
   }
@@ -164,22 +164,22 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
       }
       @Override
       double quantizeX(double raw) {
-        return decode(encode(raw));
+        return decode(encode((float) raw));
       }
 
       @Override
       double quantizeXCeil(double raw) {
-        return decode(encode(raw));
+        return decode(encode((float) raw));
       }
 
       @Override
       double quantizeY(double raw) {
-        return decode(encode(raw));
+        return decode(encode((float) raw));
       }
 
       @Override
       double quantizeYCeil(double raw) {
-        return decode(encode(raw));
+        return decode(encode((float) raw));
       }
 
       @Override
@@ -191,7 +191,7 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
       @Override
       ShapeField.DecodedTriangle encodeDecodeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) {
         byte[] encoded = new byte[7 * ShapeField.BYTES];
-        ShapeField.encodeTriangle(encoded, encode(ay), encode(ax), ab, encode(by), encode(bx), bc, encode(cy), encode(cx), ca);
+        ShapeField.encodeTriangle(encoded, encode((float) ay), encode((float) ax), ab, encode((float) by), encode((float) bx), bc, encode((float) cy), encode((float) cx), ca);
         ShapeField.DecodedTriangle triangle  = new ShapeField.DecodedTriangle();
         ShapeField.decodeTriangle(encoded, triangle);
         return triangle;
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java
index 6c884a4..5f3c7a2 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java
@@ -51,11 +51,11 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
         XYLine l = (XYLine) (shapes[i]);
         if (random.nextBoolean() && l != null) {
           int v = random.nextInt(l.numPoints() - 1);
-          x[j] = (float)l.getX(v);
-          y[j] = (float)l.getY(v);
+          x[j] = l.getX(v);
+          y[j] = l.getY(v);
         } else {
-          x[j] = (float)ShapeTestUtil.nextDouble(random);
-          y[j] = (float)ShapeTestUtil.nextDouble(random);
+          x[j] = ShapeTestUtil.nextFloat(random);
+          y[j] = ShapeTestUtil.nextFloat(random);
         }
       }
       return new XYLine(x, y);
@@ -80,7 +80,7 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
 
     @Override
     public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
-      Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
+      Component2D rectangle2D = XYGeometry.create(new XYRectangle((float) minX, (float) maxX, (float) minY, (float) maxY));
       return testComponentQuery(rectangle2D, shape);
     }
 
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestXYMultiLineShapeQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestXYMultiLineShapeQueries.java
index 399eea9..d530e66 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestXYMultiLineShapeQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestXYMultiLineShapeQueries.java
@@ -76,7 +76,7 @@ public class TestXYMultiLineShapeQueries extends BaseXYShapeTestCase {
 
     @Override
     public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
-      Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
+      Component2D rectangle2D = XYGeometry.create(new XYRectangle((float) minX, (float) maxX, (float) minY, (float) maxY));
       return testComponentQuery(rectangle2D, shape);
     }
 
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestXYMultiPointShapeQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestXYMultiPointShapeQueries.java
index 8b6db65..9c573da 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestXYMultiPointShapeQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestXYMultiPointShapeQueries.java
@@ -75,7 +75,7 @@ public class TestXYMultiPointShapeQueries extends BaseXYShapeTestCase {
 
     @Override
     public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
-      Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
+      Component2D rectangle2D = XYGeometry.create(new XYRectangle((float) minX, (float) maxX, (float) minY, (float) maxY));
       return testComponentQuery(rectangle2D, shape);
     }
 
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestXYMultiPolygonShapeQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestXYMultiPolygonShapeQueries.java
index d45c029..6803af9 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestXYMultiPolygonShapeQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestXYMultiPolygonShapeQueries.java
@@ -115,7 +115,7 @@ public class TestXYMultiPolygonShapeQueries extends BaseXYShapeTestCase {
 
     @Override
     public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
-      Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
+      Component2D rectangle2D = XYGeometry.create(new XYRectangle((float) minX, (float) maxX, (float) minY, (float) maxY));
       return testComponentQuery(rectangle2D, shape);
     }
 
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestXYPointShapeQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestXYPointShapeQueries.java
index 2085095..3b2f0df 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestXYPointShapeQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestXYPointShapeQueries.java
@@ -53,8 +53,8 @@ public class TestXYPointShapeQueries extends BaseXYShapeTestCase {
           x[j] = p.x;
           y[j] = p.y;
         } else {
-          x[j] = (float)ShapeTestUtil.nextDouble(random);
-          y[j] = (float)ShapeTestUtil.nextDouble(random);
+          x[j] = ShapeTestUtil.nextFloat(random);
+          y[j] = ShapeTestUtil.nextFloat(random);
         }
       }
       return new XYLine(x, y);
@@ -80,7 +80,7 @@ public class TestXYPointShapeQueries extends BaseXYShapeTestCase {
 
     @Override
     public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
-      Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
+      Component2D rectangle2D = XYGeometry.create(new XYRectangle((float) minX, (float) maxX, (float) minY, (float) maxY));
       return testComponentQuery(rectangle2D, shape);
     }
 
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java
index 80b3436..5da69dd 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java
@@ -66,7 +66,7 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
 
     @Override
     public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
-      Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
+      Component2D rectangle2D = XYGeometry.create(new XYRectangle((float) minX, (float) maxX, (float) minY, (float) maxY));
       return testComponentQuery(rectangle2D, shape);
     }
 
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestXYShape.java b/lucene/core/src/test/org/apache/lucene/document/TestXYShape.java
index 838ab4d..fad04cb 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestXYShape.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestXYShape.java
@@ -51,8 +51,8 @@ public class TestXYShape extends LuceneTestCase {
     }
   }
 
-  protected Query newRectQuery(String field, double minX, double maxX, double minY, double maxY) {
-    return XYShape.newBoxQuery(field, QueryRelation.INTERSECTS, (float)minX, (float)maxX, (float)minY, (float)maxY);
+  protected Query newRectQuery(String field, float minX, float maxX, float minY, float maxY) {
+    return XYShape.newBoxQuery(field, QueryRelation.INTERSECTS, minX, maxX, minY, maxY);
   }
 
   /** test we can search for a point with a standard number of vertices*/
@@ -73,8 +73,8 @@ public class TestXYShape extends LuceneTestCase {
     float x[] = new float[p.numPoints() - 1];
     float y[] = new float[p.numPoints() - 1];
     for (int i = 0; i < x.length; ++i) {
-      x[i] = (float)p.getPolyX(i);
-      y[i] = (float)p.getPolyY(i);
+      x[i] = p.getPolyX(i);
+      y[i] = p.getPolyY(i);
     }
     XYLine l = new XYLine(x, y);
     addLineToDoc(FIELDNAME, document, l);
@@ -85,28 +85,28 @@ public class TestXYShape extends LuceneTestCase {
     IndexReader reader = writer.getReader();
     writer.close();
     IndexSearcher searcher = newSearcher(reader);
-    double minX = Math.min(x[0], x[1]);
-    double minY = Math.min(y[0], y[1]);
-    double maxX = Math.max(x[0], x[1]);
-    double maxY = Math.max(y[0], y[1]);
+    float minX = Math.min(x[0], x[1]);
+    float minY = Math.min(y[0], y[1]);
+    float maxX = Math.max(x[0], x[1]);
+    float maxY = Math.max(y[0], y[1]);
     Query q = newRectQuery(FIELDNAME, minX, maxX, minY, maxY);
     assertEquals(2, searcher.count(q));
 
     // search a disjoint bbox
-    q = newRectQuery(FIELDNAME, p.minX-1d, p.minX+1, p.minY-1d, p.minY+1d);
+    q = newRectQuery(FIELDNAME, p.minX-1f, p.minX + 1f, p.minY - 1f, p.minY + 1f);
     assertEquals(0, searcher.count(q));
 
     // search w/ an intersecting polygon
     q = XYShape.newPolygonQuery(FIELDNAME, QueryRelation.INTERSECTS, new XYPolygon(
-        new float[] {(float)minX, (float)minX, (float)maxX, (float)maxX, (float)minX},
-        new float[] {(float)minY, (float)maxY, (float)maxY, (float)minY, (float)minY}
+        new float[] {minX, minX, maxX, maxX, minX},
+        new float[] {minY, maxY, maxY, minY, minY}
     ));
     assertEquals(2, searcher.count(q));
 
     // search w/ an intersecting line
     q = XYShape.newLineQuery(FIELDNAME, QueryRelation.INTERSECTS, new XYLine(
-       new float[] {(float)minX, (float)minX, (float)maxX, (float)maxX},
-       new float[] {(float)minY, (float)maxY, (float)maxY, (float)minY}
+       new float[] {minX, minX, maxX, maxX},
+       new float[] {minY, maxY, maxY, minY}
     ));
     assertEquals(2, searcher.count(q));
 
@@ -153,18 +153,18 @@ public class TestXYShape extends LuceneTestCase {
     q = newRectQuery(FIELDNAME, r1.minX, r1.maxX, r1.minY, r1.maxY);
     assertEquals(1, searcher.count(q));
     // r1 contains r2, WITHIN should match
-    q = XYShape.newBoxQuery(FIELDNAME, QueryRelation.WITHIN, (float) r1.minX, (float) r1.maxX, (float) r1.minY, (float) r1.maxY);
+    q = XYShape.newBoxQuery(FIELDNAME, QueryRelation.WITHIN, r1.minX, r1.maxX, r1.minY, r1.maxY);
     assertEquals(1, searcher.count(q));
 
     IOUtils.close(reader, dir);
   }
 
   private static boolean areBoxDisjoint(XYRectangle r1, XYRectangle r2) {
-    return ((float) r1.minX <= (float) r2.minX && (float) r1.minY <= (float) r2.minY && (float) r1.maxX >= (float) r2.maxX && (float) r1.maxY >= (float) r2.maxY);
+    return ( r1.minX <=  r2.minX &&  r1.minY <= r2.minY && r1.maxX >= r2.maxX && r1.maxY >= r2.maxY);
   }
 
   private static XYPolygon toPolygon(XYRectangle r) {
-    return new XYPolygon(new float[]{(float) r.minX, (float) r.maxX, (float) r.maxX, (float) r.minX, (float) r.minX},
-                         new float[]{(float) r.minY, (float) r.minY, (float) r.maxY, (float) r.maxY, (float) r.minY});
+    return new XYPolygon(new float[]{ r.minX, r.maxX, r.maxX, r.minX, r.minX},
+                         new float[]{ r.minY, r.minY, r.maxY, r.maxY, r.minY});
   }
 }
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestXYShapeEncoding.java b/lucene/core/src/test/org/apache/lucene/document/TestXYShapeEncoding.java
index 5d3e201..030ea6b 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestXYShapeEncoding.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestXYShapeEncoding.java
@@ -26,12 +26,12 @@ import org.apache.lucene.geo.XYPolygon;
 public class TestXYShapeEncoding extends BaseShapeEncodingTestCase {
   @Override
   protected int encodeX(double x) {
-    return XYEncodingUtils.encode(x);
+    return XYEncodingUtils.encode((float) x);
   }
 
   @Override
   protected int encodeY(double y) {
-    return XYEncodingUtils.encode(y);
+    return XYEncodingUtils.encode((float) y);
   }
 
   @Override
@@ -46,12 +46,12 @@ public class TestXYShapeEncoding extends BaseShapeEncodingTestCase {
 
   @Override
   protected double nextX() {
-    return ShapeTestUtil.nextDouble(random());
+    return ShapeTestUtil.nextFloat(random());
   }
 
   @Override
   protected double nextY() {
-    return ShapeTestUtil.nextDouble(random());
+    return ShapeTestUtil.nextFloat(random());
   }
 
   @Override
diff --git a/lucene/core/src/test/org/apache/lucene/geo/ShapeTestUtil.java b/lucene/core/src/test/org/apache/lucene/geo/ShapeTestUtil.java
index f1cc283..13e0235 100644
--- a/lucene/core/src/test/org/apache/lucene/geo/ShapeTestUtil.java
+++ b/lucene/core/src/test/org/apache/lucene/geo/ShapeTestUtil.java
@@ -41,7 +41,7 @@ public class ShapeTestUtil {
         // So the poly can cover at most 50% of the earth's surface:
         double radius = random.nextDouble() * 0.5 * Float.MAX_VALUE + 1.0;
         try {
-          return createRegularPolygon(nextDouble(random), nextDouble(random), radius, gons);
+          return createRegularPolygon(nextFloat(random), nextFloat(random), radius, gons);
         } catch (IllegalArgumentException iae) {
           // we tried to cross dateline or pole ... try again
         }
@@ -58,42 +58,53 @@ public class ShapeTestUtil {
     }
   }
 
+  public static XYLine nextLine() {
+    XYPolygon poly = ShapeTestUtil.nextPolygon();
+    float[] x = new float[poly.numPoints() - 1];
+    float[] y = new float[x.length];
+    for (int i = 0; i < x.length; ++i) {
+      x[i] = poly.getPolyX(i);
+      y[i] = poly.getPolyY(i);
+    }
+    return new XYLine(x, y);
+  }
+
   private static XYPolygon trianglePolygon(XYRectangle box) {
     final float[] polyX = new float[4];
     final float[] polyY = new float[4];
-    polyX[0] = (float)box.minX;
-    polyY[0] = (float)box.minY;
-    polyX[1] = (float)box.minX;
-    polyY[1] = (float)box.minY;
-    polyX[2] = (float)box.minX;
-    polyY[2] = (float)box.minY;
-    polyX[3] = (float)box.minX;
-    polyY[3] = (float)box.minY;
+    polyX[0] = box.minX;
+    polyY[0] = box.minY;
+    polyX[1] = box.minX;
+    polyY[1] = box.minY;
+    polyX[2] = box.minX;
+    polyY[2] = box.minY;
+    polyX[3] = box.minX;
+    polyY[3] = box.minY;
     return new XYPolygon(polyX, polyY);
   }
 
   public static XYRectangle nextBox(Random random) {
     // prevent lines instead of boxes
-    double x0 = nextDouble(random);
-    double x1 = nextDouble(random);
+    float x0 = nextFloat(random);
+    float x1 = nextFloat(random);
     while (x0 == x1) {
-      x1 = nextDouble(random);
+      x1 = nextFloat(random);
     }
     // prevent lines instead of boxes
-    double y0 = nextDouble(random);
-    double y1 = nextDouble(random);
+    float y0 = nextFloat(random);
+    float y1 = nextFloat(random);
     while (y0 == y1) {
-      y1 = nextDouble(random);
+      y1 = nextFloat(random);
     }
 
     if (x1 < x0) {
-      double x = x0;
+      float x = x0;
       x0 = x1;
       x1 = x;
     }
 
     if (y1 < y0) {
-      double y = y0;
+      float y = y0;
       y0 = y1;
       y1 = y;
     }
@@ -104,16 +115,16 @@ public class ShapeTestUtil {
   private static XYPolygon boxPolygon(XYRectangle box) {
     final float[] polyX = new float[5];
     final float[] polyY = new float[5];
-    polyX[0] = (float)box.minX;
-    polyY[0] = (float)box.minY;
-    polyX[1] = (float)box.minX;
-    polyY[1] = (float)box.minY;
-    polyX[2] = (float)box.minX;
-    polyY[2] = (float)box.minY;
-    polyX[3] = (float)box.minX;
-    polyY[3] = (float)box.minY;
-    polyX[4] = (float)box.minX;
-    polyY[4] = (float)box.minY;
+    polyX[0] = box.minX;
+    polyY[0] = box.minY;
+    polyX[1] = box.minX;
+    polyY[1] = box.minY;
+    polyX[2] = box.minX;
+    polyY[2] = box.minY;
+    polyX[3] = box.minX;
+    polyY[3] = box.minY;
+    polyX[4] = box.minX;
+    polyY[4] = box.minY;
     return new XYPolygon(polyX, polyY);
   }
 
@@ -121,8 +132,8 @@ public class ShapeTestUtil {
     // repeat until we get a poly that doesn't cross dateline:
     while (true) {
       //System.out.println("\nPOLY ITER");
-      double centerX = nextDouble(random);
-      double centerY = nextDouble(random);
+      float centerX = nextFloat(random);
+      float centerY = nextFloat(random);
       double radius = 0.1 + 20 * random.nextDouble();
       double radiusDelta = random.nextDouble();
 
@@ -136,8 +147,8 @@ public class ShapeTestUtil {
           break;
         }
         double len = radius * (1.0 - radiusDelta + radiusDelta * random.nextDouble());
-        double maxX = StrictMath.min(StrictMath.abs(Float.MAX_VALUE - centerX), StrictMath.abs(-Float.MAX_VALUE - centerX));
-        double maxY = StrictMath.min(StrictMath.abs(Float.MAX_VALUE - centerY), StrictMath.abs(-Float.MAX_VALUE - centerY));
+        float maxX = StrictMath.min(StrictMath.abs(Float.MAX_VALUE - centerX), StrictMath.abs(-Float.MAX_VALUE - centerX));
+        float maxY = StrictMath.min(StrictMath.abs(Float.MAX_VALUE - centerY), StrictMath.abs(-Float.MAX_VALUE - centerY));
 
         len = StrictMath.min(len, StrictMath.min(maxX, maxY));
 
@@ -196,8 +207,8 @@ public class ShapeTestUtil {
     return new XYPolygon(result[0], result[1]);
   }
 
-  public static double nextDouble(Random random) {
-    return BiasedNumbers.randomDoubleBetween(random, -Float.MAX_VALUE, Float.MAX_VALUE);
+  public static float nextFloat(Random random) {
+    return BiasedNumbers.randomFloatBetween(random, -Float.MAX_VALUE, Float.MAX_VALUE);
   }
 
   /** Keep it simple, we don't need to take arbitrary Random for geo tests */
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestXYLine.java b/lucene/core/src/test/org/apache/lucene/geo/TestXYLine.java
new file mode 100644
index 0000000..64d7e14
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestXYLine.java
@@ -0,0 +1,99 @@
+/*
+ * 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.geo;
+
+
+import java.util.Arrays;
+
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestXYLine extends LuceneTestCase {
+
+  /** null x not allowed */
+  public void testLineNullXs() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYLine(null, new float[] { -66, -65, -65, -66, -66 });
+    });
+    assertTrue(expected.getMessage().contains("x must not be null"));
+  }
+
+  /** null y not allowed */
+  public void testPolygonNullYs() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYLine(new float[] {18, 18, 19, 19, 18 }, null);
+    });
+    assertTrue(expected.getMessage().contains("y must not be null"));
+  }
+
+  /** needs at least 3 vertices */
+  public void testLineEnoughPoints() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYLine(new float[] {18}, new float[] { -66});
+    });
+    assertTrue(expected.getMessage().contains("at least 2 line points required"));
+  }
+
+  /** lines needs same number of x as y */
+  public void testLinesBogus() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYLine(new float[] { 18, 18, 19, 19 }, new float[] { -66, -65, -65, -66, -66 });
+    });
+    assertTrue(expected.getMessage().contains("must be equal length"));
+  }
+
+  /** line values cannot be NaN */
+  public void testLineNaN() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYLine(new float[] { 18, 18, 19, Float.NaN, 18 }, new float[] { -66, -65, -65, -66, -66 });
+    });
+    assertTrue(expected.getMessage(), expected.getMessage().contains("invalid value NaN"));
+  }
+
+  /** line values cannot be finite */
+  public void testLinePositiveInfinite() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYLine(new float[] { 18, 18, 19, 19, 18 }, new float[] { -66, Float.POSITIVE_INFINITY, -65, -66, -66 });
+    });
+    assertTrue(expected.getMessage(), expected.getMessage().contains("invalid value Inf"));
+  }
+
+  /** line values cannot be finite */
+  public void testLineNegativeInfinite() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYLine(new float[] { 18, 18, 19, 19, 18 }, new float[] { -66, -65, -65, Float.NEGATIVE_INFINITY, -66 });
+    });
+    assertTrue(expected.getMessage(), expected.getMessage().contains("invalid value -Inf"));
+  }
+
+  /** equals and hashcode */
+  public void testEqualsAndHashCode() {
+    XYLine line = ShapeTestUtil.nextLine();
+    XYLine copy = new XYLine(line.getX(), line.getY());
+    assertEquals(line, copy);
+    assertEquals(line.hashCode(), copy.hashCode());
+    XYLine otherLine = ShapeTestUtil.nextLine();
+    if (Arrays.equals(line.getX(), otherLine.getX()) == false  ||
+        Arrays.equals(line.getY(), otherLine.getY()) == false) {
+      assertNotEquals(line, otherLine);
+      assertNotEquals(line.hashCode(), otherLine.hashCode());
+    } else {
+      assertEquals(line, otherLine);
+      assertEquals(line.hashCode(), otherLine.hashCode());
+    }
+
+  }
+}
\ No newline at end of file
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestXYPoint.java b/lucene/core/src/test/org/apache/lucene/geo/TestXYPoint.java
new file mode 100644
index 0000000..7fb48a8
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestXYPoint.java
@@ -0,0 +1,77 @@
+/*
+ * 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.geo;
+
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestXYPoint extends LuceneTestCase {
+
+  /** point values cannot be NaN */
+  public void testNaN() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPoint(Float.NaN, 45.23f);
+    });
+    assertTrue(expected.getMessage().contains("invalid value NaN"));
+
+    expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPoint(43.5f, Float.NaN);
+    });
+    assertTrue(expected.getMessage().contains("invalid value NaN"));
+  }
+
+  /** point values mist be finite */
+  public void testPositiveInf() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPoint(Float.POSITIVE_INFINITY, 45.23f);
+    });
+    assertTrue(expected.getMessage().contains("invalid value Inf"));
+
+    expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPoint(43.5f, Float.POSITIVE_INFINITY);
+    });
+    assertTrue(expected.getMessage().contains("invalid value Inf"));
+  }
+
+  /** point values mist be finite */
+  public void testNegativeInf() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPoint(Float.NEGATIVE_INFINITY, 45.23f);
+    });
+    assertTrue(expected.getMessage().contains("invalid value -Inf"));
+
+    expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPoint(43.5f, Float.NEGATIVE_INFINITY);
+    });
+    assertTrue(expected.getMessage().contains("invalid value -Inf"));
+  }
+
+  /** equals and hashcode */
+  public void testEqualsAndHashCode() {
+    XYPoint point = new XYPoint(ShapeTestUtil.nextFloat(random()), ShapeTestUtil.nextFloat(random()));
+    XYPoint copy = new XYPoint(point.getX(), point.getY());
+    assertEquals(point, copy);
+    assertEquals(point.hashCode(), copy.hashCode());
+    XYPoint otherPoint = new XYPoint(ShapeTestUtil.nextFloat(random()), ShapeTestUtil.nextFloat(random()));
+    if (point.getX() != otherPoint.getX() || point.getY() != otherPoint.getY()) {
+      assertNotEquals(point, otherPoint);
+      assertNotEquals(point.hashCode(), otherPoint.hashCode());
+    } else {
+      assertEquals(point, otherPoint);
+      assertEquals(point.hashCode(), otherPoint.hashCode());
+    }
+  }
+}
\ No newline at end of file
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestXYPolygon.java b/lucene/core/src/test/org/apache/lucene/geo/TestXYPolygon.java
new file mode 100644
index 0000000..4805800
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestXYPolygon.java
@@ -0,0 +1,107 @@
+/*
+ * 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.geo;
+
+
+import java.util.Arrays;
+
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestXYPolygon extends LuceneTestCase {
+
+  /** null x not allowed */
+  public void testPolygonNullPolyLats() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPolygon(null, new float[] { -66, -65, -65, -66, -66 });
+    });
+    assertTrue(expected.getMessage().contains("x must not be null"));
+  }
+
+  /** null y not allowed */
+  public void testPolygonNullPolyLons() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPolygon(new float[] {18, 18, 19, 19, 18 }, null);
+    });
+    assertTrue(expected.getMessage().contains("y must not be null"));
+  }
+
+  /** polygon needs at least 3 vertices */
+  public void testPolygonLine() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPolygon(new float[] { 18, 18, 18 }, new float[] { -66, -65, -66 });
+    });
+    assertTrue(expected.getMessage().contains("at least 4 polygon points required"));
+  }
+
+  /** polygon needs same number of latitudes as longitudes */
+  public void testPolygonBogus() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPolygon(new float[] { 18, 18, 19, 19 }, new float[] { -66, -65, -65, -66, -66 });
+    });
+    assertTrue(expected.getMessage().contains("must be equal length"));
+  }
+
+  /** polygon must be closed */
+  public void testPolygonNotClosed() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPolygon(new float[] { 18, 18, 19, 19, 19 }, new float[] { -66, -65, -65, -66, -67 });
+    });
+    assertTrue(expected.getMessage(), expected.getMessage().contains("it must close itself"));
+  }
+
+  /** polygon values cannot be NaN */
+  public void testPolygonNaN() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPolygon(new float[] { 18, 18, 19, Float.NaN, 18 }, new float[] { -66, -65, -65, -66, -66 });
+    });
+    assertTrue(expected.getMessage(), expected.getMessage().contains("invalid value NaN"));
+  }
+
+  /** polygon values cannot be finite */
+  public void testPolygonPositiveInfinite() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPolygon(new float[] { 18, 18, 19, 19, 18 }, new float[] { -66, Float.POSITIVE_INFINITY, -65, -66, -66 });
+    });
+    assertTrue(expected.getMessage(), expected.getMessage().contains("invalid value Inf"));
+  }
+
+  /** polygon values cannot be finite */
+  public void testPolygonNegativeInfinite() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYPolygon(new float[] { 18, 18, 19, 19, 18 }, new float[] { -66, -65, -65, Float.NEGATIVE_INFINITY, -66 });
+    });
+    assertTrue(expected.getMessage(), expected.getMessage().contains("invalid value -Inf"));
+  }
+
+  /** equals and hashcode */
+  public void testEqualsAndHashCode() {
+    XYPolygon polygon = ShapeTestUtil.nextPolygon();
+    XYPolygon copy = new XYPolygon(polygon.getPolyX(), polygon.getPolyY(), polygon.getHoles());
+    assertEquals(polygon, copy);
+    assertEquals(polygon.hashCode(), copy.hashCode());
+    XYPolygon otherPolygon = ShapeTestUtil.nextPolygon();
+    if (Arrays.equals(polygon.getPolyX(), otherPolygon.getPolyX()) == false ||
+        Arrays.equals(polygon.getPolyY(), otherPolygon.getPolyY()) == false ||
+        Arrays.equals(polygon.getHoles(), otherPolygon.getHoles()) == false) {
+      assertNotEquals(polygon, otherPolygon);
+      assertNotEquals(polygon.hashCode(), otherPolygon.hashCode());
+    } else {
+      assertEquals(polygon, otherPolygon);
+      assertEquals(polygon.hashCode(), otherPolygon.hashCode());
+    }
+  }
+}
\ No newline at end of file
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle.java b/lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle.java
new file mode 100644
index 0000000..e3a4689
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle.java
@@ -0,0 +1,106 @@
+/*
+ * 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.geo;
+
+
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestXYRectangle extends LuceneTestCase {
+
+  /** maxX must be gte minX */
+  public void tesInvalidMinMaxX() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(5, 4, 3 ,4);
+    });
+    assertTrue(expected.getMessage().contains("5 > 4"));
+  }
+
+  /** maxY must be gte minY */
+  public void tesInvalidMinMaxY() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(4, 5, 5 ,4);
+    });
+    assertTrue(expected.getMessage().contains("5 > 4"));
+  }
+
+  /** rectangle values cannot be NaN */
+  public void testNaN() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(Float.NaN, 4, 3 ,4);
+    });
+    assertTrue(expected.getMessage().contains("invalid value NaN"));
+
+    expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(3, Float.NaN, 3 ,4);
+    });
+    assertTrue(expected.getMessage().contains("invalid value NaN"));
+
+    expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(3, 4, Float.NaN ,4);
+    });
+    assertTrue(expected.getMessage().contains("invalid value NaN"));
+
+    expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(3, 4, 3 , Float.NaN);
+    });
+    assertTrue(expected.getMessage().contains("invalid value NaN"));
+  }
+
+  /** rectangle values must be finite */
+  public void testPositiveInf() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(3, Float.POSITIVE_INFINITY, 3 ,4);
+    });
+    assertTrue(expected.getMessage().contains("invalid value Inf"));
+
+    expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(3, 4, 3 , Float.POSITIVE_INFINITY);
+    });
+    assertTrue(expected.getMessage().contains("invalid value Inf"));
+  }
+
+  /** rectangle values must be finite */
+  public void testNegativeInf() {
+    IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(Float.NEGATIVE_INFINITY, 4, 3 ,4);
+    });
+    assertTrue(expected.getMessage().contains("invalid value -Inf"));
+
+    expected = expectThrows(IllegalArgumentException.class, () -> {
+      new XYRectangle(3, 4, Float.NEGATIVE_INFINITY ,4);
+    });
+    assertTrue(expected.getMessage().contains("invalid value -Inf"));
+  }
+
+  /** equals and hashcode */
+  public void testEqualsAndHashCode() {
+    XYRectangle rectangle = ShapeTestUtil.nextBox(random());
+    XYRectangle copy = new XYRectangle(rectangle.minX, rectangle.maxX, rectangle.minY, rectangle.maxY);
+    assertEquals(rectangle, copy);
+    assertEquals(rectangle.hashCode(), copy.hashCode());
+    XYRectangle otherRectangle = ShapeTestUtil.nextBox(random());
+    if (rectangle.minX != otherRectangle.minX || rectangle.maxX != otherRectangle.maxX ||
+        rectangle.minY != otherRectangle.minY || rectangle.maxY != otherRectangle.maxY) {
+      assertNotEquals(rectangle, otherRectangle);
+      assertNotEquals(rectangle.hashCode(), otherRectangle.hashCode());
+    } else {
+      assertEquals(rectangle, otherRectangle);
+      assertEquals(rectangle.hashCode(), otherRectangle.hashCode());
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle2D.java b/lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle2D.java
index 4ced0b8..88b29da 100644
--- a/lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle2D.java
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle2D.java
@@ -26,7 +26,7 @@ import org.apache.lucene.util.LuceneTestCase;
 public class TestXYRectangle2D extends LuceneTestCase {
 
   public void testTriangleDisjoint() {
-    XYRectangle rectangle = new XYRectangle(0d, 1d, 0d, 1d);
+    XYRectangle rectangle = new XYRectangle(0f, 1f, 0f, 1f);
     Component2D rectangle2D = XYRectangle2D.create(rectangle);
     float ax = 4f;
     float ay = 4f;
@@ -38,7 +38,7 @@ public class TestXYRectangle2D extends LuceneTestCase {
   }
 
   public void testTriangleIntersects() {
-    XYRectangle rectangle = new XYRectangle(0d, 1d, 0d, 1d);
+    XYRectangle rectangle = new XYRectangle(0f, 1f, 0f, 1f);
     Component2D rectangle2D =  XYRectangle2D.create(rectangle);
     float ax = 0.5f;
     float ay = 0.5f;
@@ -66,12 +66,12 @@ public class TestXYRectangle2D extends LuceneTestCase {
     XYRectangle rectangle = ShapeTestUtil.nextBox(random);
     Component2D rectangle2D = XYRectangle2D.create(rectangle);
     for (int i =0; i < 100; i++) {
-      float ax = (float) ShapeTestUtil.nextDouble(random);
-      float ay = (float) ShapeTestUtil.nextDouble(random);
-      float bx = (float) ShapeTestUtil.nextDouble(random);
-      float by = (float) ShapeTestUtil.nextDouble(random);
-      float cx = (float) ShapeTestUtil.nextDouble(random);
-      float cy = (float) ShapeTestUtil.nextDouble(random);
+      float ax = ShapeTestUtil.nextFloat(random);
+      float ay = ShapeTestUtil.nextFloat(random);
+      float bx = ShapeTestUtil.nextFloat(random);
+      float by = ShapeTestUtil.nextFloat(random);
+      float cx = ShapeTestUtil.nextFloat(random);
+      float cy = ShapeTestUtil.nextFloat(random);
 
       float tMinX = StrictMath.min(StrictMath.min(ax, bx), cx);
       float tMaxX = StrictMath.max(StrictMath.max(ax, bx), cx);