diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java index 703cc67f31b..46dd777d8b0 100644 --- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java +++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java @@ -25,6 +25,7 @@ import org.apache.lucene.document.FieldType; import org.apache.lucene.spatial3d.geom.PlanetModel; import org.apache.lucene.spatial3d.geom.GeoPoint; +import org.apache.lucene.spatial3d.geom.GeoDistanceShape; /** * An per-document 3D location field. @@ -54,15 +55,15 @@ public class Geo3DDocValuesField extends Field { // 0x200000 = (maximum - minimum) * factor // So, factor = 0x200000 / (maximum - minimum) - private final double inverseMaximumValue = 1.0 / (double)(0x200000); + private final static double inverseMaximumValue = 1.0 / (double)(0x200000); - private final double inverseXFactor = (PlanetModel.WGS84.getMaximumXValue() - PlanetModel.WGS84.getMinimumXValue()) * inverseMaximumValue; - private final double inverseYFactor = (PlanetModel.WGS84.getMaximumYValue() - PlanetModel.WGS84.getMinimumYValue()) * inverseMaximumValue; - private final double inverseZFactor = (PlanetModel.WGS84.getMaximumZValue() - PlanetModel.WGS84.getMinimumZValue()) * inverseMaximumValue; + private final static double inverseXFactor = (PlanetModel.WGS84.getMaximumXValue() - PlanetModel.WGS84.getMinimumXValue()) * inverseMaximumValue; + private final static double inverseYFactor = (PlanetModel.WGS84.getMaximumYValue() - PlanetModel.WGS84.getMinimumYValue()) * inverseMaximumValue; + private final static double inverseZFactor = (PlanetModel.WGS84.getMaximumZValue() - PlanetModel.WGS84.getMinimumZValue()) * inverseMaximumValue; - private final double xFactor = 1.0 / inverseXFactor; - private final double yFactor = 1.0 / inverseYFactor; - private final double zFactor = 1.0 / inverseZFactor; + private final static double xFactor = 1.0 / inverseXFactor; + private final static double yFactor = 1.0 / inverseYFactor; + private final static double zFactor = 1.0 / inverseZFactor; /** * Type for a Geo3DDocValuesField @@ -128,13 +129,47 @@ public class Geo3DDocValuesField extends Field { ); } + /** Decode GeoPoint value from long docvalues value. + * @param docValue is the doc values value. + * @return the GeoPoint. + */ + public static GeoPoint decodePoint(final long docValue) { + return new GeoPoint(decodeX(((int)(docValue >> 42)) & 0x1FFFFF), + decodeY(((int)(docValue >> 21)) & 0x1FFFFF), + decodeZ(((int)(docValue)) & 0x1FFFFF)); + } + + /** Decode X value from long docvalues value. + * @param docValue is the doc values value. + * @return the x value. + */ + public static double decodeXValue(final long docValue) { + return decodeX(((int)(docValue >> 42)) & 0x1FFFFF); + } + + /** Decode Y value from long docvalues value. + * @param docValue is the doc values value. + * @return the y value. + */ + public static double decodeYValue(final long docValue) { + return decodeY(((int)(docValue >> 21)) & 0x1FFFFF); + } + + /** Decode Z value from long docvalues value. + * @param docValue is the doc values value. + * @return the z value. + */ + public static double decodeZValue(final long docValue) { + return decodeZ(((int)(docValue)) & 0x1FFFFF); + } + // For encoding/decoding, we generally want the following behavior: // (1) If you encode the maximum value or the minimum value, the resulting int fits in 21 bits. // (2) If you decode an encoded value, you get back the original value for both the minimum and maximum planet model values. // (3) Rounding occurs such that a small delta from the minimum and maximum planet model values still returns the same // values -- that is, these are in the center of the range of input values that should return the minimum or maximum when decoded - private int encodeX(final double x) { + private static int encodeX(final double x) { if (x > PlanetModel.WGS84.getMaximumXValue()) { throw new IllegalArgumentException("x value exceeds WGS84 maximum"); } else if (x < PlanetModel.WGS84.getMinimumXValue()) { @@ -143,11 +178,11 @@ public class Geo3DDocValuesField extends Field { return (int)Math.floor((x - PlanetModel.WGS84.getMinimumXValue()) * xFactor + 0.5); } - private double decodeX(final int x) { + private static double decodeX(final int x) { return x * inverseXFactor + PlanetModel.WGS84.getMinimumXValue(); } - private int encodeY(final double y) { + private static int encodeY(final double y) { if (y > PlanetModel.WGS84.getMaximumYValue()) { throw new IllegalArgumentException("y value exceeds WGS84 maximum"); } else if (y < PlanetModel.WGS84.getMinimumYValue()) { @@ -156,11 +191,11 @@ public class Geo3DDocValuesField extends Field { return (int)Math.floor((y - PlanetModel.WGS84.getMinimumYValue()) * yFactor + 0.5); } - private double decodeY(final int y) { + private static double decodeY(final int y) { return y * inverseYFactor + PlanetModel.WGS84.getMinimumYValue(); } - private int encodeZ(final double z) { + private static int encodeZ(final double z) { if (z > PlanetModel.WGS84.getMaximumZValue()) { throw new IllegalArgumentException("z value exceeds WGS84 maximum"); } else if (z < PlanetModel.WGS84.getMinimumZValue()) { @@ -169,7 +204,7 @@ public class Geo3DDocValuesField extends Field { return (int)Math.floor((z - PlanetModel.WGS84.getMinimumZValue()) * zFactor + 0.5); } - private double decodeZ(final int z) { + private static double decodeZ(final int z) { return z * inverseZFactor + PlanetModel.WGS84.getMinimumZValue(); } @@ -193,14 +228,60 @@ public class Geo3DDocValuesField extends Field { long currentValue = Long.valueOf((Long)fieldsData); - result.append(decodeX(((int)(currentValue >> 42)) & 0x1FFFFF)); + result.append(decodeXValue(currentValue)); result.append(','); - result.append(decodeY(((int)(currentValue >> 21)) & 0x1FFFFF)); + result.append(decodeYValue(currentValue)); result.append(','); - result.append(decodeZ(((int)(currentValue)) & 0x1FFFFF)); + result.append(decodeZValue(currentValue)); result.append('>'); return result.toString(); } + /** + * Creates a SortField for sorting by distance from a point. + *
+ * This sort orders documents by ascending distance from the location. The value returned in {@link FieldDoc} for + * the hits contains a Double instance with the distance in meters. + *
+ * If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance + * (missing values sort last). + *
+ * If a document contains multiple values for the field, the closest distance to the location is used. + * + * @param field field name. must not be null. + * @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 maxRadiusMeters is the maximum radius in meters. + * @return SortField ordering documents by distance + * @throws IllegalArgumentException if {@code field} is null or location has invalid coordinates. + */ + public static SortField newDistanceSort(final String field, final double latitude, final double longitude, final double maxRadiusMeters) { + final GeoDistanceShape shape = Geo3DUtil.fromDistance(latitude, longitude, maxRadiusMeters); + return new Geo3DPointSortField(field, shape); + } + + /** + * Creates a SortField for sorting by distance along a path. + *
+ * This sort orders documents by ascending distance along the described path. The value returned in {@link FieldDoc} for + * the hits contains a Double instance with the distance in meters. + *
+ * If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance + * (missing values sort last). + *
+ * If a document contains multiple values for the field, the closest distance to the location is used. + * + * @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 SortField ordering documents by distance + * @throws IllegalArgumentException if {@code field} is null or location has invalid coordinates. + */ + public static SortField newDistanceSort(final String field, final double[] pathLatitudes, final double[] pathLongitudes, final double pathWidthMeters) { + final GeoDistanceShape shape = Geo3DUtil.fromPath(pathLatitudes, pathLongitudes, pathWidthMeters); + return new Geo3DPointSortField(field, shape); + } + } 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 42c513cb4ed..2ff1286d434 100644 --- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java +++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java @@ -50,11 +50,6 @@ import org.apache.lucene.util.NumericUtils; * @lucene.experimental */ public final class Geo3DPoint extends Field { - /** How many radians are in one earth surface meter */ - public final static double RADIANS_PER_METER = 1.0 / PlanetModel.WGS84_MEAN; - /** How many radians are in one degree */ - public final static double RADIANS_PER_DEGREE = Math.PI / 180.0; - /** Indexing {@link FieldType}. */ public static final FieldType TYPE = new FieldType(); static { @@ -72,20 +67,10 @@ public final class Geo3DPoint extends Field { GeoUtils.checkLatitude(latitude); GeoUtils.checkLongitude(longitude); // Translate latitude/longitude to x,y,z: - final GeoPoint point = new GeoPoint(PlanetModel.WGS84, fromDegrees(latitude), fromDegrees(longitude)); + final GeoPoint point = new GeoPoint(PlanetModel.WGS84, Geo3DUtil.fromDegrees(latitude), Geo3DUtil.fromDegrees(longitude)); fillFieldsData(point.x, point.y, point.z); } - /** Converts degress to radians */ - private static double fromDegrees(final double degrees) { - return degrees * RADIANS_PER_DEGREE; - } - - /** Converts earth-surface meters to radians */ - private 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 @@ -99,12 +84,10 @@ public final class Geo3DPoint extends Field { * @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) { - GeoUtils.checkLatitude(latitude); - GeoUtils.checkLongitude(longitude); - final GeoShape shape = GeoCircleFactory.makeGeoCircle(PlanetModel.WGS84, fromDegrees(latitude), fromDegrees(longitude), fromMeters(radiusMeters)); + final GeoShape shape = Geo3DUtil.fromDistance(latitude, longitude, radiusMeters); return newShapeQuery(field, shape); } - + /** * Create a query for matching a box. *
@@ -118,12 +101,7 @@ public final class Geo3DPoint extends Field {
* @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) {
- GeoUtils.checkLatitude(minLatitude);
- GeoUtils.checkLongitude(minLongitude);
- GeoUtils.checkLatitude(maxLatitude);
- GeoUtils.checkLongitude(maxLongitude);
- final GeoShape shape = GeoBBoxFactory.makeGeoBBox(PlanetModel.WGS84,
- fromDegrees(maxLatitude), fromDegrees(minLatitude), fromDegrees(minLongitude), fromDegrees(maxLongitude));
+ final GeoShape shape = Geo3DUtil.fromBox(minLatitude, maxLatitude, minLongitude, maxLongitude);
return newShapeQuery(field, shape);
}
@@ -137,30 +115,7 @@ public final class Geo3DPoint extends Field {
* @return query matching points within this polygon
*/
public static Query newPolygonQuery(final String field, final Polygon... polygons) {
- //System.err.println("Creating polygon...");
- if (polygons.length < 1) {
- throw new IllegalArgumentException("need at least one polygon");
- }
- final GeoShape shape;
- if (polygons.length == 1) {
- final GeoShape component = fromPolygon(polygons[0]);
- if (component == null) {
- // Polygon is degenerate
- shape = new GeoCompositePolygon();
- } else {
- shape = component;
- }
- } else {
- final GeoCompositePolygon poly = new GeoCompositePolygon();
- for (final Polygon p : polygons) {
- final GeoPolygon component = fromPolygon(p);
- if (component != null) {
- poly.addShape(component);
- }
- }
- shape = poly;
- }
- //System.err.println("...done");
+ final GeoShape shape = Geo3DUtil.fromPolygon(polygons);
return newShapeQuery(field, shape);
}
@@ -177,7 +132,7 @@ public final class Geo3DPoint extends Field {
if (polygons.length < 1) {
throw new IllegalArgumentException("need at least one polygon");
}
- final GeoShape shape = fromLargePolygon(polygons);
+ final GeoShape shape = Geo3DUtil.fromLargePolygon(polygons);
return newShapeQuery(field, shape);
}
@@ -191,94 +146,10 @@ public final class Geo3DPoint extends Field {
* @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];
- GeoUtils.checkLatitude(latitude);
- GeoUtils.checkLongitude(longitude);
- points[i] = new GeoPoint(PlanetModel.WGS84, fromDegrees(latitude), fromDegrees(longitude));
- }
- final GeoShape shape = GeoPathFactory.makeGeoPath(PlanetModel.WGS84, fromMeters(pathWidthMeters), points);
+ final GeoShape shape = Geo3DUtil.fromPath(pathLatitudes, pathLongitudes, pathWidthMeters);
return newShapeQuery(field, shape);
}
-
- /**
- * Convert a Polygon object to a large GeoPolygon.
- * @param polygons is the list of polygons to convert.
- * @return the large GeoPolygon.
- */
- private static GeoPolygon fromLargePolygon(final Polygon... polygons) {
- return GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.WGS84, convertToDescription(polygons));
- }
-
- /**
- * Convert a list of polygons to a list of polygon descriptions.
- * @param polygons is the list of polygons to convert.
- * @return the list of polygon descriptions.
- */
- private static List
+ * When the least competitive item on the priority queue changes (setBottom), we recompute
+ * a bounding box representing competitive distance to the top-N. Then in compareBottom, we can
+ * quickly reject hits based on bounding box alone without computing distance for every element.
+ */
+class Geo3DPointDistanceComparator extends FieldComparator