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 aa = parseMap(body); - args.setDistPrecision(readDouble(aa.remove("distPrec"))); + args.setDistErrPct(readDouble(aa.remove("distErrPct"))); + args.setDistErr(readDouble(aa.remove("distErr"))); if (!aa.isEmpty()) { throw new InvalidSpatialArgument("unused parameters: " + aa, null); } } } + args.validate(); return args; } @@ -92,6 +109,8 @@ return v == null ? defaultValue : Boolean.parseBoolean(v); } + /** Parses "a=b c=d f" (whitespace separated) into name-value pairs. If there + * is no '=' as in 'f' above then it's short for f=f. */ protected static Map parseMap(String body) { Map map = new HashMap(); StringTokenizer st = new StringTokenizer(body, " \n\t"); Index: lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java (revision 1380426) +++ lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java (revision ) @@ -25,7 +25,6 @@ import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldType; import org.apache.lucene.index.FieldInfo; -import org.apache.lucene.index.IndexableField; import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.prefix.tree.Node; @@ -45,7 +44,7 @@ protected final SpatialPrefixTree grid; private final Map provider = new ConcurrentHashMap(); protected int defaultFieldValuesArrayLen = 2; - protected double distErrPct = SpatialArgs.DEFAULT_DIST_PRECISION; + protected double distErrPct = SpatialArgs.DEFAULT_DISTERRPCT;// [ 0 TO 1 ] public PrefixTreeStrategy(SpatialPrefixTree grid, String fieldName) { super(grid.getSpatialContext(), fieldName); @@ -57,21 +56,25 @@ this.defaultFieldValuesArrayLen = defaultFieldValuesArrayLen; } - /** See {@link SpatialPrefixTree#getMaxLevelForPrecision(com.spatial4j.core.shape.Shape, double)}. */ + public double getDistErrPct() { + return distErrPct; + } + + /** See {@link org.apache.lucene.spatial.query.SpatialArgs#getDistErrPct()}. */ public void setDistErrPct(double distErrPct) { this.distErrPct = distErrPct; } @Override public Field[] createIndexableFields(Shape shape) { - int detailLevel = grid.getMaxLevelForPrecision(shape,distErrPct); + int detailLevel = grid.getLevelForDistance(SpatialArgs.calcDistanceFromErrPct(shape, distErrPct, ctx)); List cells = grid.getNodes(shape, detailLevel, true);//true=intermediates cells //If shape isn't a point, add a full-resolution center-point so that - // PrefixFieldCacheProvider has the center-points. + // PointPrefixTreeFieldCacheProvider has the center-points. // TODO index each center of a multi-point? Yes/no? if (!(shape instanceof Point)) { Point ctr = shape.getCenter(); - //TODO should be smarter; don't index 2 tokens for this in CellTokenizer. Harmless though. + //TODO should be smarter; don't index 2 tokens for this in CellTokenStream. Harmless though. cells.add(grid.getNodes(ctr,grid.getMaxLevels(),false).get(0)); } Index: lucene/spatial/src/java/org/apache/lucene/spatial/prefix/TermQueryPrefixTreeStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/prefix/TermQueryPrefixTreeStrategy.java (revision 1380426) +++ lucene/spatial/src/java/org/apache/lucene/spatial/prefix/TermQueryPrefixTreeStrategy.java (revision ) @@ -48,7 +48,7 @@ throw new UnsupportedSpatialOperation(op); Shape shape = args.getShape(); - int detailLevel = grid.getMaxLevelForPrecision(shape, args.getDistPrecision()); + int detailLevel = grid.getLevelForDistance(args.resolveDistErr(ctx, distErrPct)); List cells = grid.getNodes(shape, detailLevel, false); TermsFilter filter = new TermsFilter(); for (Node cell : cells) { Index: lucene/spatial/src/java/org/apache/lucene/spatial/prefix/RecursivePrefixTreeStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/prefix/RecursivePrefixTreeStrategy.java (revision 1380426) +++ lucene/spatial/src/java/org/apache/lucene/spatial/prefix/RecursivePrefixTreeStrategy.java (revision ) @@ -56,7 +56,7 @@ Shape shape = args.getShape(); - int detailLevel = grid.getMaxLevelForPrecision(shape,args.getDistPrecision()); + int detailLevel = grid.getLevelForDistance(args.resolveDistErr(ctx, distErrPct)); return new RecursivePrefixTreeFilter( getFieldName(), grid,shape, prefixGridScanLevel, detailLevel);