Upgraded to Spatial4j 0.3
This commit is contained in:
parent
0fadbf2177
commit
6fc0b83e07
2
pom.xml
2
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 + "]");
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue