Upgraded to Spatial4j 0.3

This commit is contained in:
Chris Male 2012-09-20 22:25:09 +12:00 committed by Shay Banon
parent 0fadbf2177
commit 6fc0b83e07
16 changed files with 168 additions and 121 deletions

View File

@ -155,7 +155,7 @@
<dependency>
<groupId>com.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
<version>0.2</version>
<version>0.3</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

View File

@ -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");

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);
}
/**

View File

@ -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;

View File

@ -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<Node> 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;
}
}

View File

@ -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<Node> 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<Node> 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<Node> 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<Node> 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();

View File

@ -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

View File

@ -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<Node> {
public static final byte LEAF_BYTE = '+';//NOTE: must sort before letters & numbers
@ -150,7 +152,7 @@ public abstract class Node implements Comparable<Node> {
}
List<Node> copy = new ArrayList<Node>(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;

View File

@ -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
@ -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<Node> cells = new ArrayList<Node>(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<Node> getSubCells() {
List<Node> cells = new ArrayList<Node>(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
}

View File

@ -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.
*
* <p/>
* 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);

View File

@ -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<String> {
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<String> {
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 + "]");
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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);