diff --git a/pom.xml b/pom.xml
index 5bc41568ae3..b844c24d030 100644
--- a/pom.xml
+++ b/pom.xml
@@ -155,7 +155,7 @@
com.spatial4j
spatial4j
- 0.2
+ 0.3
compile
true
diff --git a/src/main/java/org/elasticsearch/common/geo/GeoJSONShapeParser.java b/src/main/java/org/elasticsearch/common/geo/GeoJSONShapeParser.java
index 7dafbfdbad7..daceb2f12de 100644
--- a/src/main/java/org/elasticsearch/common/geo/GeoJSONShapeParser.java
+++ b/src/main/java/org/elasticsearch/common/geo/GeoJSONShapeParser.java
@@ -20,9 +20,9 @@
package org.elasticsearch.common.geo;
import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.impl.RectangleImpl;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
-import com.spatial4j.core.shape.simple.RectangleImpl;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
@@ -138,9 +138,9 @@ public class GeoJSONShapeParser {
*/
private static Shape buildShape(String shapeType, CoordinateNode node) {
if ("point".equals(shapeType)) {
- return new JtsPoint(GEOMETRY_FACTORY.createPoint(node.coordinate));
+ return new JtsPoint(GEOMETRY_FACTORY.createPoint(node.coordinate), GeoShapeConstants.SPATIAL_CONTEXT);
} else if ("linestring".equals(shapeType)) {
- return new JtsGeometry(GEOMETRY_FACTORY.createLineString(toCoordinates(node)));
+ return new JtsGeometry(GEOMETRY_FACTORY.createLineString(toCoordinates(node)), GeoShapeConstants.SPATIAL_CONTEXT, true);
} else if ("polygon".equals(shapeType)) {
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(toCoordinates(node.children.get(0)));
LinearRing[] holes = null;
@@ -150,12 +150,12 @@ public class GeoJSONShapeParser {
holes[i] = GEOMETRY_FACTORY.createLinearRing(toCoordinates(node.children.get(i + 1)));
}
}
- return new JtsGeometry(GEOMETRY_FACTORY.createPolygon(shell, holes));
+ return new JtsGeometry(GEOMETRY_FACTORY.createPolygon(shell, holes), GeoShapeConstants.SPATIAL_CONTEXT, true);
} else if ("multipoint".equals(shapeType)) {
- return new JtsGeometry(GEOMETRY_FACTORY.createMultiPoint(toCoordinates(node)));
+ return new JtsGeometry(GEOMETRY_FACTORY.createMultiPoint(toCoordinates(node)), GeoShapeConstants.SPATIAL_CONTEXT, true);
} else if ("envelope".equals(shapeType)) {
Coordinate[] coordinates = toCoordinates(node);
- return new RectangleImpl(coordinates[0].x, coordinates[1].x, coordinates[1].y, coordinates[0].y);
+ return new RectangleImpl(coordinates[0].x, coordinates[1].x, coordinates[1].y, coordinates[0].y, GeoShapeConstants.SPATIAL_CONTEXT);
}
throw new UnsupportedOperationException("ShapeType [" + shapeType + "] not supported");
diff --git a/src/main/java/org/elasticsearch/common/geo/GeoJSONShapeSerializer.java b/src/main/java/org/elasticsearch/common/geo/GeoJSONShapeSerializer.java
index 34843da22b5..e95df6fec84 100644
--- a/src/main/java/org/elasticsearch/common/geo/GeoJSONShapeSerializer.java
+++ b/src/main/java/org/elasticsearch/common/geo/GeoJSONShapeSerializer.java
@@ -50,7 +50,7 @@ public class GeoJSONShapeSerializer {
*/
public static void serialize(Shape shape, XContentBuilder builder) throws IOException {
if (shape instanceof JtsGeometry) {
- Geometry geometry = ((JtsGeometry) shape).geo;
+ Geometry geometry = ((JtsGeometry) shape).getGeom();
if (geometry instanceof Point) {
serializePoint((Point) geometry, builder);
} else if (geometry instanceof LineString) {
diff --git a/src/main/java/org/elasticsearch/common/geo/GeoShapeConstants.java b/src/main/java/org/elasticsearch/common/geo/GeoShapeConstants.java
new file mode 100644
index 00000000000..355ffa4af50
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/GeoShapeConstants.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.common.geo;
+
+import com.spatial4j.core.context.jts.JtsSpatialContext;
+
+/**
+ * Common constants through the GeoShape codebase
+ */
+public interface GeoShapeConstants {
+
+ public static final JtsSpatialContext SPATIAL_CONTEXT = new JtsSpatialContext(true);
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/ShapeBuilder.java b/src/main/java/org/elasticsearch/common/geo/ShapeBuilder.java
index 256dc8ef274..33e50460166 100644
--- a/src/main/java/org/elasticsearch/common/geo/ShapeBuilder.java
+++ b/src/main/java/org/elasticsearch/common/geo/ShapeBuilder.java
@@ -22,10 +22,10 @@ package org.elasticsearch.common.geo;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.impl.PointImpl;
+import com.spatial4j.core.shape.impl.RectangleImpl;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
-import com.spatial4j.core.shape.simple.PointImpl;
-import com.spatial4j.core.shape.simple.RectangleImpl;
import com.vividsolutions.jts.geom.*;
import java.util.ArrayList;
@@ -50,7 +50,7 @@ public class ShapeBuilder {
* @return Point with the latitude and longitude
*/
public static Point newPoint(double lon, double lat) {
- return new PointImpl(lon, lat);
+ return new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
}
/**
@@ -80,9 +80,9 @@ public class ShapeBuilder {
*/
public static Geometry toJTSGeometry(Shape shape) {
if (shape instanceof JtsGeometry) {
- return ((JtsGeometry) shape).geo;
+ return ((JtsGeometry) shape).getGeom();
} else if (shape instanceof JtsPoint) {
- return ((JtsPoint) shape).getJtsPoint();
+ return ((JtsPoint) shape).getGeom();
} else if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape;
@@ -119,7 +119,7 @@ public class ShapeBuilder {
* @return this
*/
public RectangleBuilder topLeft(double lon, double lat) {
- this.topLeft = new PointImpl(lon, lat);
+ this.topLeft = new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
return this;
}
@@ -131,7 +131,7 @@ public class ShapeBuilder {
* @return this
*/
public RectangleBuilder bottomRight(double lon, double lat) {
- this.bottomRight = new PointImpl(lon, lat);
+ this.bottomRight = new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
return this;
}
@@ -141,7 +141,7 @@ public class ShapeBuilder {
* @return Built Rectangle
*/
public Rectangle build() {
- return new RectangleImpl(topLeft.getX(), bottomRight.getX(), bottomRight.getY(), topLeft.getY());
+ return new RectangleImpl(topLeft.getX(), bottomRight.getX(), bottomRight.getY(), topLeft.getY(), GeoShapeConstants.SPATIAL_CONTEXT);
}
}
@@ -160,7 +160,7 @@ public class ShapeBuilder {
* @return this
*/
public PolygonBuilder point(double lon, double lat) {
- points.add(new PointImpl(lon, lat));
+ points.add(new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT));
return this;
}
@@ -170,7 +170,7 @@ public class ShapeBuilder {
* @return Built polygon
*/
public Shape build() {
- return new JtsGeometry(toPolygon());
+ return new JtsGeometry(toPolygon(), GeoShapeConstants.SPATIAL_CONTEXT, true);
}
/**
diff --git a/src/main/java/org/elasticsearch/common/geo/ShapesAvailability.java b/src/main/java/org/elasticsearch/common/geo/ShapesAvailability.java
index bef043d5d77..0ce2087f5f3 100644
--- a/src/main/java/org/elasticsearch/common/geo/ShapesAvailability.java
+++ b/src/main/java/org/elasticsearch/common/geo/ShapesAvailability.java
@@ -19,7 +19,7 @@
package org.elasticsearch.common.geo;
-import com.spatial4j.core.shape.simple.PointImpl;
+import com.spatial4j.core.shape.impl.PointImpl;
import com.vividsolutions.jts.geom.GeometryFactory;
/**
@@ -32,7 +32,7 @@ public class ShapesAvailability {
static {
boolean xSPATIAL4J_AVAILABLE;
try {
- new PointImpl(0, 0);
+ new PointImpl(0, 0, GeoShapeConstants.SPATIAL_CONTEXT);
xSPATIAL4J_AVAILABLE = true;
} catch (Throwable t) {
xSPATIAL4J_AVAILABLE = false;
diff --git a/src/main/java/org/elasticsearch/common/lucene/spatial/SpatialStrategy.java b/src/main/java/org/elasticsearch/common/lucene/spatial/SpatialStrategy.java
index a2c90834ed7..5ee2110402f 100644
--- a/src/main/java/org/elasticsearch/common/lucene/spatial/SpatialStrategy.java
+++ b/src/main/java/org/elasticsearch/common/lucene/spatial/SpatialStrategy.java
@@ -1,15 +1,18 @@
package org.elasticsearch.common.lucene.spatial;
+import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.shape.Point;
+import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
+import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.lucene.spatial.prefix.NodeTokenStream;
import org.elasticsearch.common.lucene.spatial.prefix.tree.Node;
import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree;
-import org.elasticsearch.index.cache.filter.FilterCache;
import org.elasticsearch.index.mapper.FieldMapper;
import java.util.List;
@@ -54,7 +57,8 @@ public abstract class SpatialStrategy {
* @return Fieldable for indexing the Shape
*/
public Fieldable createField(Shape shape) {
- int detailLevel = prefixTree.getMaxLevelForPrecision(shape, distanceErrorPct);
+ int detailLevel = prefixTree.getLevelForDistance(
+ calcDistanceFromErrPct(shape, distanceErrorPct, GeoShapeConstants.SPATIAL_CONTEXT));
List nodes = prefixTree.getNodes(shape, detailLevel, true);
NodeTokenStream tokenStream = nodeTokenStream.get();
tokenStream.setNodes(nodes);
@@ -185,4 +189,29 @@ public abstract class SpatialStrategy {
public SpatialPrefixTree getPrefixTree() {
return prefixTree;
}
+
+ /**
+ * 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).
+ */
+ protected final 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());
+ return diagonalDist * 0.5 * distErrPct;
+ }
}
diff --git a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/TermQueryPrefixTreeStrategy.java b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/TermQueryPrefixTreeStrategy.java
index 70878c7ea22..4eb001d85a0 100644
--- a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/TermQueryPrefixTreeStrategy.java
+++ b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/TermQueryPrefixTreeStrategy.java
@@ -7,17 +7,15 @@ import com.vividsolutions.jts.operation.buffer.BufferOp;
import com.vividsolutions.jts.operation.buffer.BufferParameters;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
-import org.elasticsearch.common.lucene.search.OrFilter;
+import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.lucene.search.TermFilter;
import org.elasticsearch.common.lucene.search.XBooleanFilter;
import org.elasticsearch.common.lucene.spatial.SpatialStrategy;
import org.elasticsearch.common.lucene.spatial.prefix.tree.Node;
import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.elasticsearch.common.geo.ShapeBuilder;
-import org.elasticsearch.index.cache.filter.FilterCache;
import org.elasticsearch.index.mapper.FieldMapper;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -46,7 +44,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
*/
@Override
public Filter createIntersectsFilter(Shape shape) {
- int detailLevel = getPrefixTree().getMaxLevelForPrecision(shape, getDistanceErrorPct());
+ int detailLevel = getPrefixTree().getLevelForDistance(
+ calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
List nodes = getPrefixTree().getNodes(shape, detailLevel, false);
Term[] nodeTerms = new Term[nodes.size()];
@@ -61,7 +60,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
*/
@Override
public Query createIntersectsQuery(Shape shape) {
- int detailLevel = getPrefixTree().getMaxLevelForPrecision(shape, getDistanceErrorPct());
+ int detailLevel = getPrefixTree().getLevelForDistance(
+ calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
List nodes = getPrefixTree().getNodes(shape, detailLevel, false);
BooleanQuery query = new BooleanQuery();
@@ -78,7 +78,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
*/
@Override
public Filter createDisjointFilter(Shape shape) {
- int detailLevel = getPrefixTree().getMaxLevelForPrecision(shape, getDistanceErrorPct());
+ int detailLevel = getPrefixTree().getLevelForDistance(
+ calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
List nodes = getPrefixTree().getNodes(shape, detailLevel, false);
XBooleanFilter filter = new XBooleanFilter();
@@ -94,7 +95,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
*/
@Override
public Query createDisjointQuery(Shape shape) {
- int detailLevel = getPrefixTree().getMaxLevelForPrecision(shape, getDistanceErrorPct());
+ int detailLevel = getPrefixTree().getLevelForDistance(
+ calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
List nodes = getPrefixTree().getNodes(shape, detailLevel, false);
BooleanQuery query = new BooleanQuery();
@@ -115,10 +117,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
Filter intersectsFilter = createIntersectsFilter(shape);
Geometry shapeGeometry = ShapeBuilder.toJTSGeometry(shape);
- // TODO: Need some way to detect if having the buffer is going to push the shape over the dateline
- // and throw an error in this instance
Geometry buffer = BufferOp.bufferOp(shapeGeometry, CONTAINS_BUFFER_DISTANCE, BUFFER_PARAMETERS);
- Shape bufferedShape = new JtsGeometry(buffer.difference(shapeGeometry));
+ Shape bufferedShape = new JtsGeometry(buffer.difference(shapeGeometry), GeoShapeConstants.SPATIAL_CONTEXT, true);
Filter bufferedFilter = createIntersectsFilter(bufferedShape);
XBooleanFilter filter = new XBooleanFilter();
@@ -137,7 +137,7 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
Geometry shapeGeometry = ShapeBuilder.toJTSGeometry(shape);
Geometry buffer = BufferOp.bufferOp(shapeGeometry, CONTAINS_BUFFER_DISTANCE, BUFFER_PARAMETERS);
- Shape bufferedShape = new JtsGeometry(buffer.difference(shapeGeometry));
+ Shape bufferedShape = new JtsGeometry(buffer.difference(shapeGeometry), GeoShapeConstants.SPATIAL_CONTEXT, true);
Query bufferedQuery = createIntersectsQuery(bufferedShape);
BooleanQuery query = new BooleanQuery();
diff --git a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/GeohashPrefixTree.java b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/GeohashPrefixTree.java
index 3bf0019f6e8..c9fc79c0977 100644
--- a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/GeohashPrefixTree.java
+++ b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/GeohashPrefixTree.java
@@ -18,10 +18,10 @@
package org.elasticsearch.common.lucene.spatial.prefix.tree;
import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.io.GeohashUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
-import com.spatial4j.core.util.GeohashUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -30,6 +30,8 @@ import java.util.List;
/**
* A SpatialPrefixGrid based on Geohashes. Uses {@link GeohashUtils} to do all the geohash work.
+ *
+ * @lucene.experimental
*/
public class GeohashPrefixTree extends SpatialPrefixTree {
@@ -37,19 +39,23 @@ public class GeohashPrefixTree extends SpatialPrefixTree {
super(ctx, maxLevels);
Rectangle bounds = ctx.getWorldBounds();
if (bounds.getMinX() != -180)
- throw new IllegalArgumentException("Geohash only supports lat-lon world bounds. Got "+bounds);
+ throw new IllegalArgumentException("Geohash only supports lat-lon world bounds. Got " + bounds);
int MAXP = getMaxLevelsPossible();
if (maxLevels <= 0 || maxLevels > MAXP)
- throw new IllegalArgumentException("maxLen must be [1-"+MAXP+"] but got "+ maxLevels);
+ throw new IllegalArgumentException("maxLen must be [1-" + MAXP + "] but got " + maxLevels);
}
- /** Any more than this and there's no point (double lat & lon are the same). */
+ /**
+ * Any more than this and there's no point (double lat & lon are the same).
+ */
public static int getMaxLevelsPossible() {
return GeohashUtils.MAX_PRECISION;
}
@Override
public int getLevelForDistance(double dist) {
+ if (dist == 0)
+ return maxLevels;//short circuit
final int level = GeohashUtils.lookupHashLenForWidthHeight(dist, dist);
return Math.max(Math.min(level, maxLevels), 1);
}
@@ -107,7 +113,7 @@ public class GeohashPrefixTree extends SpatialPrefixTree {
@Override
public Node getSubCell(Point p) {
- return GeohashPrefixTree.this.getNode(p,getLevel()+1);//not performant!
+ return GeohashPrefixTree.this.getNode(p, getLevel() + 1);//not performant!
}
private Shape shape;//cache
diff --git a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/Node.java b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/Node.java
index c22acf6d5b2..f8ff8c8037e 100644
--- a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/Node.java
+++ b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/Node.java
@@ -28,6 +28,8 @@ import java.util.List;
/**
* Represents a grid cell. These are not necessarily threadsafe, although new Cell("") (world cell) must be.
+ *
+ * @lucene.experimental
*/
public abstract class Node implements Comparable {
public static final byte LEAF_BYTE = '+';//NOTE: must sort before letters & numbers
@@ -150,7 +152,7 @@ public abstract class Node implements Comparable {
}
List copy = new ArrayList(cells.size());//copy since cells contractually isn't modifiable
for (Node cell : cells) {
- SpatialRelation rel = cell.getShape().relate(shapeFilter, spatialPrefixTree.ctx);
+ SpatialRelation rel = cell.getShape().relate(shapeFilter);
if (rel == SpatialRelation.DISJOINT)
continue;
cell.shapeRel = rel;
diff --git a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/QuadPrefixTree.java b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/QuadPrefixTree.java
index 6dc6dcb3367..01678ca8b4f 100644
--- a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/QuadPrefixTree.java
+++ b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/QuadPrefixTree.java
@@ -22,14 +22,17 @@ import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
-import com.spatial4j.core.shape.simple.PointImpl;
+import java.io.PrintStream;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
-
+/**
+ * @lucene.experimental
+ */
public class QuadPrefixTree extends SpatialPrefixTree {
public static final int MAX_LEVELS_POSSIBLE = 50;//not really sure how big this should be
@@ -47,8 +50,8 @@ public class QuadPrefixTree extends SpatialPrefixTree {
final double[] levelW;
final double[] levelH;
- final int[] levelS; // side
- final int[] levelN; // number
+ final int[] levelS; // side
+ final int[] levelN; // number
public QuadPrefixTree(
SpatialContext ctx, Rectangle bounds, int maxLevels) {
@@ -65,10 +68,10 @@ public class QuadPrefixTree extends SpatialPrefixTree {
gridW = xmax - xmin;
gridH = ymax - ymin;
- this.xmid = xmin + gridW/2.0;
- this.ymid = ymin + gridH/2.0;
- levelW[0] = gridW/2.0;
- levelH[0] = gridH/2.0;
+ this.xmid = xmin + gridW / 2.0;
+ this.ymid = ymin + gridH / 2.0;
+ levelW[0] = gridW / 2.0;
+ levelH[0] = gridH / 2.0;
levelS[0] = 2;
levelN[0] = 4;
@@ -89,24 +92,26 @@ public class QuadPrefixTree extends SpatialPrefixTree {
this(ctx, ctx.getWorldBounds(), maxLevels);
}
- public void printInfo() {
- NumberFormat nf = NumberFormat.getNumberInstance();
+ public void printInfo(PrintStream out) {
+ NumberFormat nf = NumberFormat.getNumberInstance(Locale.ROOT);
nf.setMaximumFractionDigits(5);
nf.setMinimumFractionDigits(5);
nf.setMinimumIntegerDigits(3);
for (int i = 0; i < maxLevels; i++) {
- System.out.println(i + "]\t" + nf.format(levelW[i]) + "\t" + nf.format(levelH[i]) + "\t" +
+ out.println(i + "]\t" + nf.format(levelW[i]) + "\t" + nf.format(levelH[i]) + "\t" +
levelS[i] + "\t" + (levelS[i] * levelS[i]));
}
}
@Override
public int getLevelForDistance(double dist) {
- for (int i = 1; i < maxLevels; i++) {
+ 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]) {
- return i;
+ if (dist > levelW[i] && dist > levelH[i]) {
+ return i + 1;
}
}
return maxLevels;
@@ -115,7 +120,7 @@ public class QuadPrefixTree extends SpatialPrefixTree {
@Override
public Node getNode(Point p, int level) {
List cells = new ArrayList(1);
- build(xmid, ymid, 0, cells, new StringBuilder(), new PointImpl(p.getX(),p.getY()), level);
+ build(xmid, ymid, 0, cells, new StringBuilder(), ctx.makePoint(p.getX(), p.getY()), level);
return cells.get(0);//note cells could be longer if p on edge
}
@@ -176,21 +181,21 @@ public class QuadPrefixTree extends SpatialPrefixTree {
double h = levelH[level] / 2;
int strlen = str.length();
- Rectangle rectangle = ctx.makeRect(cx - w, cx + w, cy - h, cy + h);
- SpatialRelation v = shape.relate(rectangle, ctx);
+ Rectangle rectangle = ctx.makeRectangle(cx - w, cx + w, cy - h, cy + h);
+ SpatialRelation v = shape.relate(rectangle);
if (SpatialRelation.CONTAINS == v) {
str.append(c);
//str.append(SpatialPrefixGrid.COVER);
- matches.add(new QuadCell(str.toString(),v.transpose()));
+ matches.add(new QuadCell(str.toString(), v.transpose()));
} else if (SpatialRelation.DISJOINT == v) {
// nothing
} else { // SpatialRelation.WITHIN, SpatialRelation.INTERSECTS
str.append(c);
- int nextLevel = level+1;
+ int nextLevel = level + 1;
if (nextLevel >= maxLevel) {
//str.append(SpatialPrefixGrid.INTERSECTS);
- matches.add(new QuadCell(str.toString(),v.transpose()));
+ matches.add(new QuadCell(str.toString(), v.transpose()));
} else {
build(cx, cy, nextLevel, matches, str, shape, maxLevel);
}
@@ -222,10 +227,10 @@ public class QuadPrefixTree extends SpatialPrefixTree {
@Override
public Collection getSubCells() {
List cells = new ArrayList(4);
- cells.add(new QuadCell(getTokenString()+"A"));
- cells.add(new QuadCell(getTokenString()+"B"));
- cells.add(new QuadCell(getTokenString()+"C"));
- cells.add(new QuadCell(getTokenString()+"D"));
+ cells.add(new QuadCell(getTokenString() + "A"));
+ cells.add(new QuadCell(getTokenString() + "B"));
+ cells.add(new QuadCell(getTokenString() + "C"));
+ cells.add(new QuadCell(getTokenString() + "D"));
return cells;
}
@@ -236,7 +241,7 @@ public class QuadPrefixTree extends SpatialPrefixTree {
@Override
public Node getSubCell(Point p) {
- return QuadPrefixTree.this.getNode(p,getLevel()+1);//not performant!
+ return QuadPrefixTree.this.getNode(p, getLevel() + 1);//not performant!
}
private Shape shape;//cache
@@ -262,8 +267,7 @@ public class QuadPrefixTree extends SpatialPrefixTree {
ymin += levelH[i];
} else if ('C' == c || 'c' == c) {
// nothing really
- }
- else if('D' == c || 'd' == c) {
+ } else if ('D' == c || 'd' == c) {
xmin += levelW[i];
} else {
throw new RuntimeException("unexpected char: " + c);
@@ -272,13 +276,13 @@ public class QuadPrefixTree extends SpatialPrefixTree {
int len = token.length();
double width, height;
if (len > 0) {
- width = levelW[len-1];
- height = levelH[len-1];
+ width = levelW[len - 1];
+ height = levelH[len - 1];
} else {
width = gridW;
height = gridH;
}
- return ctx.makeRect(xmin, xmin + width, ymin, ymin + height);
+ return ctx.makeRectangle(xmin, xmin + width, ymin, ymin + height);
}
}//QuadCell
}
diff --git a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/SpatialPrefixTree.java b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/SpatialPrefixTree.java
index d72718a1c57..0b345e8d9c5 100644
--- a/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/SpatialPrefixTree.java
+++ b/src/main/java/org/elasticsearch/common/lucene/spatial/prefix/tree/SpatialPrefixTree.java
@@ -28,10 +28,12 @@ import java.util.Collections;
import java.util.List;
/**
- * A Spatial Prefix Tree, or Trie, which decomposes shapes into prefixed strings at variable lengths corresponding to
+ * A spatial Prefix Tree, or Trie, which decomposes shapes into prefixed strings at variable lengths corresponding to
* variable precision. Each string corresponds to a spatial region.
- *
+ *
* Implementations of this class should be thread-safe and immutable once initialized.
+ *
+ * @lucene.experimental
*/
public abstract class SpatialPrefixTree {
@@ -61,34 +63,14 @@ public abstract class SpatialPrefixTree {
}
/**
- * See {@link com.spatial4j.core.query.SpatialArgs#getDistPrecision()}.
- * A grid level looked up via {@link #getLevelForDistance(double)} is returned.
- *
- * @param shape
- * @param precision 0-0.5
- * @return 1-maxLevels
- */
- public int getMaxLevelForPrecision(Shape shape, double precision) {
- if (precision < 0 || precision > 0.5) {
- throw new IllegalArgumentException("Precision " + precision + " must be between [0-0.5]");
- }
- if (precision == 0 || shape instanceof Point) {
- return maxLevels;
- }
- double bboxArea = shape.getBoundingBox().getArea();
- if (bboxArea == 0) {
- return maxLevels;
- }
- double avgSideLenFromCenter = Math.sqrt(bboxArea) / 2;
- return getLevelForDistance(avgSideLenFromCenter * precision);
- }
-
- /**
- * Returns the level of the smallest grid size with a side length that is greater or equal to the provided
- * distance.
+ * Returns the level of the largest grid in which its longest side is less
+ * than or equal to the provided distance (in degrees). Consequently {@code
+ * dist} acts as an error epsilon declaring the amount of detail needed in the
+ * grid, such that you can get a grid with just the right amount of
+ * precision.
*
* @param dist >= 0
- * @return level [1-maxLevels]
+ * @return level [1 to maxLevels]
*/
public abstract int getLevelForDistance(double dist);
diff --git a/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java
index 7721a329557..a9782c6c2d3 100644
--- a/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java
+++ b/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java
@@ -1,14 +1,12 @@
package org.elasticsearch.index.mapper.geo;
-import com.spatial4j.core.context.SpatialContext;
-import com.spatial4j.core.context.jts.JtsSpatialContext;
-import com.spatial4j.core.distance.DistanceUnits;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.index.FieldInfo;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoJSONShapeParser;
+import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.lucene.spatial.SpatialStrategy;
import org.elasticsearch.common.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
import org.elasticsearch.common.lucene.spatial.prefix.tree.GeohashPrefixTree;
@@ -44,9 +42,6 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper {
public static final String CONTENT_TYPE = "geo_shape";
- // TODO: Unsure if the units actually matter since we dont do distance calculations
- public static final SpatialContext SPATIAL_CONTEXT = new JtsSpatialContext(DistanceUnits.KILOMETERS);
-
public static class Names {
public static final String TREE = "tree";
public static final String TREE_LEVELS = "tree_levels";
@@ -93,10 +88,10 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper {
public GeoShapeFieldMapper build(BuilderContext context) {
if (tree.equals(Names.GEOHASH)) {
int levels = treeLevels != 0 ? treeLevels : Defaults.GEOHASH_LEVELS;
- prefixTree = new GeohashPrefixTree(SPATIAL_CONTEXT, levels);
+ prefixTree = new GeohashPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, levels);
} else if (tree.equals(Names.QUADTREE)) {
int levels = treeLevels != 0 ? treeLevels : Defaults.QUADTREE_LEVELS;
- prefixTree = new QuadPrefixTree(SPATIAL_CONTEXT, levels);
+ prefixTree = new QuadPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, levels);
} else {
throw new ElasticSearchIllegalArgumentException("Unknown prefix tree type [" + tree + "]");
}
diff --git a/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeParserTests.java b/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeParserTests.java
index 83957d1c0b0..99fa03657f1 100644
--- a/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeParserTests.java
+++ b/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeParserTests.java
@@ -5,6 +5,7 @@ import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.*;
import org.elasticsearch.common.geo.GeoJSONShapeParser;
+import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
@@ -30,7 +31,7 @@ public class GeoJSONShapeParserTests {
.endObject().string();
Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
- assertGeometryEquals(new JtsPoint(expected), pointGeoJson);
+ assertGeometryEquals(new JtsPoint(expected, GeoShapeConstants.SPATIAL_CONTEXT), pointGeoJson);
}
@Test
@@ -48,7 +49,7 @@ public class GeoJSONShapeParserTests {
LineString expected = GEOMETRY_FACTORY.createLineString(
lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
- assertGeometryEquals(new JtsGeometry(expected), lineGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), lineGeoJson);
}
@Test
@@ -75,7 +76,7 @@ public class GeoJSONShapeParserTests {
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
- assertGeometryEquals(new JtsGeometry(expected), polygonGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), polygonGeoJson);
}
@Test
@@ -119,7 +120,7 @@ public class GeoJSONShapeParserTests {
holes[0] = GEOMETRY_FACTORY.createLinearRing(
holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes);
- assertGeometryEquals(new JtsGeometry(expected), polygonGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), polygonGeoJson);
}
@Test
@@ -137,7 +138,7 @@ public class GeoJSONShapeParserTests {
MultiPoint expected = GEOMETRY_FACTORY.createMultiPoint(
multiPointCoordinates.toArray(new Coordinate[multiPointCoordinates.size()]));
- assertGeometryEquals(new JtsGeometry(expected), multiPointGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), multiPointGeoJson);
}
private void assertGeometryEquals(Shape expected, String geoJson) throws IOException {
diff --git a/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeSerializerTests.java b/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeSerializerTests.java
index 007076566bd..344ba0cb88d 100644
--- a/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeSerializerTests.java
+++ b/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeSerializerTests.java
@@ -5,6 +5,7 @@ import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.*;
import org.elasticsearch.common.geo.GeoJSONShapeSerializer;
+import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.testng.annotations.Test;
@@ -29,7 +30,7 @@ public class GeoJSONShapeSerializerTests {
.endObject();
Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
- assertSerializationEquals(expected, new JtsPoint(point));
+ assertSerializationEquals(expected, new JtsPoint(point, GeoShapeConstants.SPATIAL_CONTEXT));
}
@Test
@@ -48,7 +49,7 @@ public class GeoJSONShapeSerializerTests {
LineString lineString = GEOMETRY_FACTORY.createLineString(
lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
- assertSerializationEquals(expected, new JtsGeometry(lineString));
+ assertSerializationEquals(expected, new JtsGeometry(lineString, GeoShapeConstants.SPATIAL_CONTEXT, false));
}
@Test
@@ -76,7 +77,7 @@ public class GeoJSONShapeSerializerTests {
shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon polygon = GEOMETRY_FACTORY.createPolygon(shell, null);
- assertSerializationEquals(expected, new JtsGeometry(polygon));
+ assertSerializationEquals(expected, new JtsGeometry(polygon, GeoShapeConstants.SPATIAL_CONTEXT, false));
}
@Test
@@ -121,7 +122,7 @@ public class GeoJSONShapeSerializerTests {
holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon polygon = GEOMETRY_FACTORY.createPolygon(shell, holes);
- assertSerializationEquals(expected, new JtsGeometry(polygon));
+ assertSerializationEquals(expected, new JtsGeometry(polygon, GeoShapeConstants.SPATIAL_CONTEXT, false));
}
@Test
@@ -140,7 +141,7 @@ public class GeoJSONShapeSerializerTests {
MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPoint(
multiPointCoordinates.toArray(new Coordinate[multiPointCoordinates.size()]));
- assertSerializationEquals(expected, new JtsGeometry(multiPoint));
+ assertSerializationEquals(expected, new JtsGeometry(multiPoint, GeoShapeConstants.SPATIAL_CONTEXT, false));
}
private void assertSerializationEquals(XContentBuilder expected, Shape shape) throws IOException {
diff --git a/src/test/java/org/elasticsearch/test/unit/common/lucene/spatial/prefix/TermQueryPrefixTreeStrategyTests.java b/src/test/java/org/elasticsearch/test/unit/common/lucene/spatial/prefix/TermQueryPrefixTreeStrategyTests.java
index 34e0ade9d5a..6ad27e6c7a5 100644
--- a/src/test/java/org/elasticsearch/test/unit/common/lucene/spatial/prefix/TermQueryPrefixTreeStrategyTests.java
+++ b/src/test/java/org/elasticsearch/test/unit/common/lucene/spatial/prefix/TermQueryPrefixTreeStrategyTests.java
@@ -1,8 +1,5 @@
package org.elasticsearch.test.unit.common.lucene.spatial.prefix;
-import com.spatial4j.core.context.SpatialContext;
-import com.spatial4j.core.context.jts.JtsSpatialContext;
-import com.spatial4j.core.distance.DistanceUnits;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.analysis.KeywordAnalyzer;
@@ -16,6 +13,7 @@ import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.Version;
+import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
import org.elasticsearch.common.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.elasticsearch.common.lucene.spatial.prefix.tree.QuadPrefixTree;
@@ -37,13 +35,12 @@ import static org.testng.Assert.assertTrue;
*/
public class TermQueryPrefixTreeStrategyTests {
- private static final SpatialContext SPATIAL_CONTEXT = new JtsSpatialContext(DistanceUnits.KILOMETERS);
// TODO: Randomize the implementation choice
private static final SpatialPrefixTree QUAD_PREFIX_TREE =
- new QuadPrefixTree(SPATIAL_CONTEXT, QuadPrefixTree.DEFAULT_MAX_LEVELS);
+ new QuadPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, QuadPrefixTree.DEFAULT_MAX_LEVELS);
private static final SpatialPrefixTree GEOHASH_PREFIX_TREE
- = new GeohashPrefixTree(SPATIAL_CONTEXT, GeohashPrefixTree.getMaxLevelsPossible());
+ = new GeohashPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, GeohashPrefixTree.getMaxLevelsPossible());
private static final TermQueryPrefixTreeStrategy STRATEGY = new TermQueryPrefixTreeStrategy(new FieldMapper.Names("shape"), GEOHASH_PREFIX_TREE, 0.025);