Index: lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/GeohashPrefixTree.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/GeohashPrefixTree.java (revision 1380426) +++ lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/GeohashPrefixTree.java (revision ) @@ -67,6 +67,8 @@ @Override public int getLevelForDistance(double dist) { + if (dist == 0) + return maxLevels;//short circuit final int level = lookupHashLenForWidthHeight(dist, dist); return Math.max(Math.min(level, maxLevels), 1); } Index: lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgs.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgs.java (revision 1380426) +++ lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgs.java (revision ) @@ -17,9 +17,10 @@ * limitations under the License. */ -import java.util.Locale; - +import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.exception.InvalidSpatialArgument; +import com.spatial4j.core.shape.Point; +import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; /** @@ -29,36 +30,74 @@ */ public class SpatialArgs { - public static final double DEFAULT_DIST_PRECISION = 0.025d; + public static final double DEFAULT_DISTERRPCT = 0.025d; private SpatialOperation operation; private Shape shape; - private double distPrecision = DEFAULT_DIST_PRECISION; + private Double distErrPct; + private Double distErr; - public SpatialArgs(SpatialOperation operation) { - this.operation = operation; - } - public SpatialArgs(SpatialOperation operation, Shape shape) { + if (operation == null || shape == null) + throw new NullPointerException("operation and shape are required"); this.operation = operation; this.shape = shape; } + /** + * Computes the distance given a shape and the {@code distErrPct}. The + * algorithm is the fraction of the distance from the center of the query + * shape to its furthest bounding box corner. + * + * @param shape Mandatory. + * @param distErrPct 0 to 0.5 + * @param ctx Mandatory + * @return A distance (in degrees). + */ + public static double calcDistanceFromErrPct(Shape shape, double distErrPct, SpatialContext ctx) { + if (distErrPct < 0 || distErrPct > 0.5) { + throw new IllegalArgumentException("distErrPct " + distErrPct + " must be between [0 to 0.5]"); + } + if (distErrPct == 0 || shape instanceof Point) { + return 0; + } + Rectangle bbox = shape.getBoundingBox(); + //The diagonal distance should be the same computed from any opposite corner, + // and this is the longest distance that might be occurring within the shape. + double diagonalDist = ctx.getDistCalc().distance( + ctx.makePoint(bbox.getMinX(), bbox.getMinY()), bbox.getMaxX(), bbox.getMaxY()); + //convert to degrees //TODO not needed in Spatial4j 0.3 + diagonalDist = ctx.getDistCalc().distanceToDegrees(diagonalDist); + return diagonalDist * 0.5 * distErrPct; + } + + /** + * Gets the error distance that specifies how precise the query shape is. This + * looks at {@link #getDistErr()}, {@link #getDistErrPct()}, and {@code + * defaultDistErrPct}. + * @param ctx + * @param defaultDistErrPct 0 to 0.5 + * @return >= 0 + */ + public double resolveDistErr(SpatialContext ctx, double defaultDistErrPct) { + if (distErr != null) + return distErr; + double distErrPct = (this.distErrPct != null ? this.distErrPct : defaultDistErrPct); + return calcDistanceFromErrPct(shape, distErrPct, ctx); + } + /** Check if the arguments make sense -- throw an exception if not */ public void validate() throws InvalidSpatialArgument { if (operation.isTargetNeedsArea() && !shape.hasArea()) { throw new InvalidSpatialArgument(operation + " only supports geometry with area"); } + if (distErr != null && distErrPct != null) + throw new InvalidSpatialArgument("Only distErr or distErrPct can be specified."); } @Override public String toString() { - StringBuilder str = new StringBuilder(); - str.append(operation.getName()).append('('); - str.append(shape.toString()); - str.append(" distPrec=").append(String.format(Locale.ROOT, "%.2f%%", distPrecision / 100d)); - str.append(')'); - return str.toString(); + return SpatialArgsParser.writeSpatialArgs(this); } //------------------------------------------------ @@ -85,22 +124,33 @@ } /** - * A measure of acceptable error of the shape. It is specified as the - * fraction of the distance from the center of the query shape to its furthest - * bounding box corner. This effectively inflates the size of the shape but - * should not shrink it. - *
- * The default is {@link #DEFAULT_DIST_PRECISION} + * A measure of acceptable error of the shape as a fraction. This effectively + * inflates the size of the shape but should not shrink it. * * @return 0 to 0.5 + * @see #calcDistanceFromErrPct(com.spatial4j.core.shape.Shape, double, + * com.spatial4j.core.context.SpatialContext) */ - public Double getDistPrecision() { - return distPrecision; + public Double getDistErrPct() { + return distErrPct; } - public void setDistPrecision(Double distPrecision) { - if (distPrecision != null) - this.distPrecision = distPrecision; + public void setDistErrPct(Double distErrPct) { + if (distErrPct != null) + this.distErrPct = distErrPct; } + /** + * The acceptable error of the shape. This effectively inflates the + * size of the shape but should not shrink it. + * + * @return >= 0 + */ + public Double getDistErr() { + return distErr; + } + + public void setDistErr(Double distErr) { + this.distErr = distErr; + } } Index: lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java (revision 1380426) +++ lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java (revision ) @@ -28,7 +28,6 @@ import org.apache.lucene.document.Field; import org.apache.lucene.document.StoredField; import org.apache.lucene.document.StringField; -import org.apache.lucene.index.IndexableField; import org.apache.lucene.spatial.SpatialMatchConcern; import org.apache.lucene.spatial.StrategyTestCase; import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; @@ -178,7 +177,7 @@ private SpatialArgs q(Point pt, double dist, double distPrec) { Shape shape = ctx.makeCircle(pt,dist); SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects,shape); - args.setDistPrecision(distPrec); + args.setDistErrPct(distPrec); return args; } Index: lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/QuadPrefixTree.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/QuadPrefixTree.java (revision 1380426) +++ lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/QuadPrefixTree.java (revision ) @@ -122,6 +122,8 @@ @Override public int getLevelForDistance(double dist) { + if (dist == 0)//short circuit + return maxLevels; for (int i = 0; i < maxLevels-1; i++) { //note: level[i] is actually a lookup for level i+1 if(dist > levelW[i] && dist > levelH[i]) { Index: lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTree.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTree.java (revision 1380426) +++ lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTree.java (revision ) @@ -18,7 +18,6 @@ */ import com.spatial4j.core.context.SpatialContext; -import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; @@ -62,30 +61,6 @@ @Override public String toString() { return getClass().getSimpleName() + "(maxLevels:" + maxLevels + ",ctx:" + ctx + ")"; - } - - /** - * See {@link org.apache.lucene.spatial.query.SpatialArgs#getDistPrecision()}. - * A grid level looked up via {@link #getLevelForDistance(double)} is returned. - * - * @param precision 0 to 0.5 - * @return 1 to maxLevels - */ - public int getMaxLevelForPrecision(Shape shape, double precision) { - if (precision < 0 || precision > 0.5) { - throw new IllegalArgumentException("Precision " + precision + " must be between [0 to 0.5]"); - } - if (precision == 0 || shape instanceof Point) { - return maxLevels; - } - Rectangle bbox = shape.getBoundingBox(); - //The diagonal distance should be the same computed from any opposite corner, - // and this is the longest distance that might be occurring within the shape. - double diagonalDist = ctx.getDistCalc().distance( - ctx.makePoint(bbox.getMinX(), bbox.getMinY()), bbox.getMaxX(), bbox.getMaxY()); - //convert to degrees //TODO not needed in Spatial4j 0.3 - diagonalDist = ctx.getDistCalc().distanceToDegrees(diagonalDist); - return getLevelForDistance(diagonalDist * 0.5 * precision); } /** Index: lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java (revision 1380426) +++ lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java (revision ) @@ -23,6 +23,7 @@ import com.spatial4j.core.shape.Shape; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; @@ -44,8 +45,22 @@ */ public class SpatialArgsParser { + /** Writes a close approximation to the parsed input format. */ + static String writeSpatialArgs(SpatialArgs args) { + StringBuilder str = new StringBuilder(); + str.append(args.getOperation().getName()); + str.append('('); + str.append(args.getShape().toString()); + if (args.getDistErrPct() != null) + str.append(" distErrPct=").append(String.format(Locale.ROOT, "%.2f%%", args.getDistErrPct() * 100d)); + if (args.getDistErr() != null) + str.append(" distErr=").append(args.getDistErr()); + str.append(')'); + return str.toString(); + } + /** - * Parses a string such as "Intersects(-10,20,-8,22) distPec=0.025". + * Parses a string such as "Intersects(-10,20,-8,22) distErrPct=0.025". * * @param v The string to parse. Mandatory. * @param ctx The spatial context. Mandatory. @@ -75,12 +90,14 @@ body = v.substring(edx + 1).trim(); if (body.length() > 0) { Map