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> <dependency>
<groupId>com.spatial4j</groupId> <groupId>com.spatial4j</groupId>
<artifactId>spatial4j</artifactId> <artifactId>spatial4j</artifactId>
<version>0.2</version> <version>0.3</version>
<scope>compile</scope> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>

View File

@ -20,9 +20,9 @@
package org.elasticsearch.common.geo; package org.elasticsearch.common.geo;
import com.spatial4j.core.shape.Shape; 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.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint; 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.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.LinearRing;
@ -138,9 +138,9 @@ public class GeoJSONShapeParser {
*/ */
private static Shape buildShape(String shapeType, CoordinateNode node) { private static Shape buildShape(String shapeType, CoordinateNode node) {
if ("point".equals(shapeType)) { 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)) { } 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)) { } else if ("polygon".equals(shapeType)) {
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(toCoordinates(node.children.get(0))); LinearRing shell = GEOMETRY_FACTORY.createLinearRing(toCoordinates(node.children.get(0)));
LinearRing[] holes = null; LinearRing[] holes = null;
@ -150,12 +150,12 @@ public class GeoJSONShapeParser {
holes[i] = GEOMETRY_FACTORY.createLinearRing(toCoordinates(node.children.get(i + 1))); 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)) { } 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)) { } else if ("envelope".equals(shapeType)) {
Coordinate[] coordinates = toCoordinates(node); 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"); 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 { public static void serialize(Shape shape, XContentBuilder builder) throws IOException {
if (shape instanceof JtsGeometry) { if (shape instanceof JtsGeometry) {
Geometry geometry = ((JtsGeometry) shape).geo; Geometry geometry = ((JtsGeometry) shape).getGeom();
if (geometry instanceof Point) { if (geometry instanceof Point) {
serializePoint((Point) geometry, builder); serializePoint((Point) geometry, builder);
} else if (geometry instanceof LineString) { } 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.Point;
import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape; 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.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint; 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 com.vividsolutions.jts.geom.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -50,7 +50,7 @@ public class ShapeBuilder {
* @return Point with the latitude and longitude * @return Point with the latitude and longitude
*/ */
public static Point newPoint(double lon, double lat) { 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) { public static Geometry toJTSGeometry(Shape shape) {
if (shape instanceof JtsGeometry) { if (shape instanceof JtsGeometry) {
return ((JtsGeometry) shape).geo; return ((JtsGeometry) shape).getGeom();
} else if (shape instanceof JtsPoint) { } else if (shape instanceof JtsPoint) {
return ((JtsPoint) shape).getJtsPoint(); return ((JtsPoint) shape).getGeom();
} else if (shape instanceof Rectangle) { } else if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape; Rectangle rectangle = (Rectangle) shape;
@ -119,7 +119,7 @@ public class ShapeBuilder {
* @return this * @return this
*/ */
public RectangleBuilder topLeft(double lon, double lat) { public RectangleBuilder topLeft(double lon, double lat) {
this.topLeft = new PointImpl(lon, lat); this.topLeft = new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
return this; return this;
} }
@ -131,7 +131,7 @@ public class ShapeBuilder {
* @return this * @return this
*/ */
public RectangleBuilder bottomRight(double lon, double lat) { public RectangleBuilder bottomRight(double lon, double lat) {
this.bottomRight = new PointImpl(lon, lat); this.bottomRight = new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
return this; return this;
} }
@ -141,7 +141,7 @@ public class ShapeBuilder {
* @return Built Rectangle * @return Built Rectangle
*/ */
public Rectangle build() { 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 * @return this
*/ */
public PolygonBuilder point(double lon, double lat) { public PolygonBuilder point(double lon, double lat) {
points.add(new PointImpl(lon, lat)); points.add(new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT));
return this; return this;
} }
@ -170,7 +170,7 @@ public class ShapeBuilder {
* @return Built polygon * @return Built polygon
*/ */
public Shape build() { 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; 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; import com.vividsolutions.jts.geom.GeometryFactory;
/** /**
@ -32,7 +32,7 @@ public class ShapesAvailability {
static { static {
boolean xSPATIAL4J_AVAILABLE; boolean xSPATIAL4J_AVAILABLE;
try { try {
new PointImpl(0, 0); new PointImpl(0, 0, GeoShapeConstants.SPATIAL_CONTEXT);
xSPATIAL4J_AVAILABLE = true; xSPATIAL4J_AVAILABLE = true;
} catch (Throwable t) { } catch (Throwable t) {
xSPATIAL4J_AVAILABLE = false; xSPATIAL4J_AVAILABLE = false;

View File

@ -1,15 +1,18 @@
package org.elasticsearch.common.lucene.spatial; 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 com.spatial4j.core.shape.Shape;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable; import org.apache.lucene.document.Fieldable;
import org.apache.lucene.search.Filter; import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.lucene.spatial.prefix.NodeTokenStream; 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.Node;
import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.elasticsearch.index.cache.filter.FilterCache;
import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.FieldMapper;
import java.util.List; import java.util.List;
@ -54,7 +57,8 @@ public abstract class SpatialStrategy {
* @return Fieldable for indexing the Shape * @return Fieldable for indexing the Shape
*/ */
public Fieldable createField(Shape 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); List<Node> nodes = prefixTree.getNodes(shape, detailLevel, true);
NodeTokenStream tokenStream = nodeTokenStream.get(); NodeTokenStream tokenStream = nodeTokenStream.get();
tokenStream.setNodes(nodes); tokenStream.setNodes(nodes);
@ -185,4 +189,29 @@ public abstract class SpatialStrategy {
public SpatialPrefixTree getPrefixTree() { public SpatialPrefixTree getPrefixTree() {
return prefixTree; 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 com.vividsolutions.jts.operation.buffer.BufferParameters;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.*; 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.TermFilter;
import org.elasticsearch.common.lucene.search.XBooleanFilter; import org.elasticsearch.common.lucene.search.XBooleanFilter;
import org.elasticsearch.common.lucene.spatial.SpatialStrategy; import org.elasticsearch.common.lucene.spatial.SpatialStrategy;
import org.elasticsearch.common.lucene.spatial.prefix.tree.Node; import org.elasticsearch.common.lucene.spatial.prefix.tree.Node;
import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.elasticsearch.common.geo.ShapeBuilder; import org.elasticsearch.common.geo.ShapeBuilder;
import org.elasticsearch.index.cache.filter.FilterCache;
import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.FieldMapper;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -46,7 +44,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
*/ */
@Override @Override
public Filter createIntersectsFilter(Shape shape) { 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); List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
Term[] nodeTerms = new Term[nodes.size()]; Term[] nodeTerms = new Term[nodes.size()];
@ -61,7 +60,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
*/ */
@Override @Override
public Query createIntersectsQuery(Shape shape) { 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); List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
BooleanQuery query = new BooleanQuery(); BooleanQuery query = new BooleanQuery();
@ -78,7 +78,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
*/ */
@Override @Override
public Filter createDisjointFilter(Shape shape) { 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); List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
XBooleanFilter filter = new XBooleanFilter(); XBooleanFilter filter = new XBooleanFilter();
@ -94,7 +95,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
*/ */
@Override @Override
public Query createDisjointQuery(Shape shape) { 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); List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
BooleanQuery query = new BooleanQuery(); BooleanQuery query = new BooleanQuery();
@ -115,10 +117,8 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
Filter intersectsFilter = createIntersectsFilter(shape); Filter intersectsFilter = createIntersectsFilter(shape);
Geometry shapeGeometry = ShapeBuilder.toJTSGeometry(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); 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); Filter bufferedFilter = createIntersectsFilter(bufferedShape);
XBooleanFilter filter = new XBooleanFilter(); XBooleanFilter filter = new XBooleanFilter();
@ -137,7 +137,7 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
Geometry shapeGeometry = ShapeBuilder.toJTSGeometry(shape); Geometry shapeGeometry = ShapeBuilder.toJTSGeometry(shape);
Geometry buffer = BufferOp.bufferOp(shapeGeometry, CONTAINS_BUFFER_DISTANCE, BUFFER_PARAMETERS); 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); Query bufferedQuery = createIntersectsQuery(bufferedShape);
BooleanQuery query = new BooleanQuery(); BooleanQuery query = new BooleanQuery();

View File

@ -18,10 +18,10 @@
package org.elasticsearch.common.lucene.spatial.prefix.tree; package org.elasticsearch.common.lucene.spatial.prefix.tree;
import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.io.GeohashUtils;
import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.util.GeohashUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; 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. * A SpatialPrefixGrid based on Geohashes. Uses {@link GeohashUtils} to do all the geohash work.
*
* @lucene.experimental
*/ */
public class GeohashPrefixTree extends SpatialPrefixTree { public class GeohashPrefixTree extends SpatialPrefixTree {
@ -43,13 +45,17 @@ public class GeohashPrefixTree extends SpatialPrefixTree {
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() { public static int getMaxLevelsPossible() {
return GeohashUtils.MAX_PRECISION; return GeohashUtils.MAX_PRECISION;
} }
@Override @Override
public int getLevelForDistance(double dist) { public int getLevelForDistance(double dist) {
if (dist == 0)
return maxLevels;//short circuit
final int level = GeohashUtils.lookupHashLenForWidthHeight(dist, dist); final int level = GeohashUtils.lookupHashLenForWidthHeight(dist, dist);
return Math.max(Math.min(level, maxLevels), 1); return Math.max(Math.min(level, maxLevels), 1);
} }

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. * 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 abstract class Node implements Comparable<Node> {
public static final byte LEAF_BYTE = '+';//NOTE: must sort before letters & numbers 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 List<Node> copy = new ArrayList<Node>(cells.size());//copy since cells contractually isn't modifiable
for (Node cell : cells) { for (Node cell : cells) {
SpatialRelation rel = cell.getShape().relate(shapeFilter, spatialPrefixTree.ctx); SpatialRelation rel = cell.getShape().relate(shapeFilter);
if (rel == SpatialRelation.DISJOINT) if (rel == SpatialRelation.DISJOINT)
continue; continue;
cell.shapeRel = rel; 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.Rectangle;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation; import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.simple.PointImpl;
import java.io.PrintStream;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
/**
* @lucene.experimental
*/
public class QuadPrefixTree extends SpatialPrefixTree { public class QuadPrefixTree extends SpatialPrefixTree {
public static final int MAX_LEVELS_POSSIBLE = 50;//not really sure how big this should be public static final int MAX_LEVELS_POSSIBLE = 50;//not really sure how big this should be
@ -89,24 +92,26 @@ public class QuadPrefixTree extends SpatialPrefixTree {
this(ctx, ctx.getWorldBounds(), maxLevels); this(ctx, ctx.getWorldBounds(), maxLevels);
} }
public void printInfo() { public void printInfo(PrintStream out) {
NumberFormat nf = NumberFormat.getNumberInstance(); NumberFormat nf = NumberFormat.getNumberInstance(Locale.ROOT);
nf.setMaximumFractionDigits(5); nf.setMaximumFractionDigits(5);
nf.setMinimumFractionDigits(5); nf.setMinimumFractionDigits(5);
nf.setMinimumIntegerDigits(3); nf.setMinimumIntegerDigits(3);
for (int i = 0; i < maxLevels; i++) { 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])); levelS[i] + "\t" + (levelS[i] * levelS[i]));
} }
} }
@Override @Override
public int getLevelForDistance(double dist) { 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 //note: level[i] is actually a lookup for level i+1
if(dist > levelW[i] || dist > levelH[i]) { if (dist > levelW[i] && dist > levelH[i]) {
return i; return i + 1;
} }
} }
return maxLevels; return maxLevels;
@ -115,7 +120,7 @@ public class QuadPrefixTree extends SpatialPrefixTree {
@Override @Override
public Node getNode(Point p, int level) { public Node getNode(Point p, int level) {
List<Node> cells = new ArrayList<Node>(1); 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 return cells.get(0);//note cells could be longer if p on edge
} }
@ -176,8 +181,8 @@ public class QuadPrefixTree extends SpatialPrefixTree {
double h = levelH[level] / 2; double h = levelH[level] / 2;
int strlen = str.length(); int strlen = str.length();
Rectangle rectangle = ctx.makeRect(cx - w, cx + w, cy - h, cy + h); Rectangle rectangle = ctx.makeRectangle(cx - w, cx + w, cy - h, cy + h);
SpatialRelation v = shape.relate(rectangle, ctx); SpatialRelation v = shape.relate(rectangle);
if (SpatialRelation.CONTAINS == v) { if (SpatialRelation.CONTAINS == v) {
str.append(c); str.append(c);
//str.append(SpatialPrefixGrid.COVER); //str.append(SpatialPrefixGrid.COVER);
@ -262,8 +267,7 @@ public class QuadPrefixTree extends SpatialPrefixTree {
ymin += levelH[i]; ymin += levelH[i];
} else if ('C' == c || 'c' == c) { } else if ('C' == c || 'c' == c) {
// nothing really // nothing really
} } else if ('D' == c || 'd' == c) {
else if('D' == c || 'd' == c) {
xmin += levelW[i]; xmin += levelW[i];
} else { } else {
throw new RuntimeException("unexpected char: " + c); throw new RuntimeException("unexpected char: " + c);
@ -278,7 +282,7 @@ public class QuadPrefixTree extends SpatialPrefixTree {
width = gridW; width = gridW;
height = gridH; height = gridH;
} }
return ctx.makeRect(xmin, xmin + width, ymin, ymin + height); return ctx.makeRectangle(xmin, xmin + width, ymin, ymin + height);
} }
}//QuadCell }//QuadCell
} }

View File

@ -28,10 +28,12 @@ import java.util.Collections;
import java.util.List; 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. * variable precision. Each string corresponds to a spatial region.
* * <p/>
* Implementations of this class should be thread-safe and immutable once initialized. * Implementations of this class should be thread-safe and immutable once initialized.
*
* @lucene.experimental
*/ */
public abstract class SpatialPrefixTree { public abstract class SpatialPrefixTree {
@ -61,34 +63,14 @@ public abstract class SpatialPrefixTree {
} }
/** /**
* See {@link com.spatial4j.core.query.SpatialArgs#getDistPrecision()}. * Returns the level of the largest grid in which its longest side is less
* A grid level looked up via {@link #getLevelForDistance(double)} is returned. * 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
* @param shape * grid, such that you can get a grid with just the right amount of
* @param precision 0-0.5 * precision.
* @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.
* *
* @param dist >= 0 * @param dist >= 0
* @return level [1-maxLevels] * @return level [1 to maxLevels]
*/ */
public abstract int getLevelForDistance(double dist); public abstract int getLevelForDistance(double dist);

View File

@ -1,14 +1,12 @@
package org.elasticsearch.index.mapper.geo; 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.Field;
import org.apache.lucene.document.Fieldable; import org.apache.lucene.document.Fieldable;
import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.FieldInfo;
import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoJSONShapeParser; 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.SpatialStrategy;
import org.elasticsearch.common.lucene.spatial.prefix.TermQueryPrefixTreeStrategy; 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.GeohashPrefixTree;
@ -44,9 +42,6 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
public static final String CONTENT_TYPE = "geo_shape"; 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 class Names {
public static final String TREE = "tree"; public static final String TREE = "tree";
public static final String TREE_LEVELS = "tree_levels"; public static final String TREE_LEVELS = "tree_levels";
@ -93,10 +88,10 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
public GeoShapeFieldMapper build(BuilderContext context) { public GeoShapeFieldMapper build(BuilderContext context) {
if (tree.equals(Names.GEOHASH)) { if (tree.equals(Names.GEOHASH)) {
int levels = treeLevels != 0 ? treeLevels : Defaults.GEOHASH_LEVELS; 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)) { } else if (tree.equals(Names.QUADTREE)) {
int levels = treeLevels != 0 ? treeLevels : Defaults.QUADTREE_LEVELS; int levels = treeLevels != 0 ? treeLevels : Defaults.QUADTREE_LEVELS;
prefixTree = new QuadPrefixTree(SPATIAL_CONTEXT, levels); prefixTree = new QuadPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, levels);
} else { } else {
throw new ElasticSearchIllegalArgumentException("Unknown prefix tree type [" + tree + "]"); 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.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.*; import com.vividsolutions.jts.geom.*;
import org.elasticsearch.common.geo.GeoJSONShapeParser; import org.elasticsearch.common.geo.GeoJSONShapeParser;
import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.json.JsonXContent;
@ -30,7 +31,7 @@ public class GeoJSONShapeParserTests {
.endObject().string(); .endObject().string();
Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); 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 @Test
@ -48,7 +49,7 @@ public class GeoJSONShapeParserTests {
LineString expected = GEOMETRY_FACTORY.createLineString( LineString expected = GEOMETRY_FACTORY.createLineString(
lineCoordinates.toArray(new Coordinate[lineCoordinates.size()])); lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
assertGeometryEquals(new JtsGeometry(expected), lineGeoJson); assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), lineGeoJson);
} }
@Test @Test
@ -75,7 +76,7 @@ public class GeoJSONShapeParserTests {
LinearRing shell = GEOMETRY_FACTORY.createLinearRing( LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null); Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
assertGeometryEquals(new JtsGeometry(expected), polygonGeoJson); assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), polygonGeoJson);
} }
@Test @Test
@ -119,7 +120,7 @@ public class GeoJSONShapeParserTests {
holes[0] = GEOMETRY_FACTORY.createLinearRing( holes[0] = GEOMETRY_FACTORY.createLinearRing(
holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes); Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes);
assertGeometryEquals(new JtsGeometry(expected), polygonGeoJson); assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), polygonGeoJson);
} }
@Test @Test
@ -137,7 +138,7 @@ public class GeoJSONShapeParserTests {
MultiPoint expected = GEOMETRY_FACTORY.createMultiPoint( MultiPoint expected = GEOMETRY_FACTORY.createMultiPoint(
multiPointCoordinates.toArray(new Coordinate[multiPointCoordinates.size()])); 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 { 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.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.*; import com.vividsolutions.jts.geom.*;
import org.elasticsearch.common.geo.GeoJSONShapeSerializer; import org.elasticsearch.common.geo.GeoJSONShapeSerializer;
import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -29,7 +30,7 @@ public class GeoJSONShapeSerializerTests {
.endObject(); .endObject();
Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); 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 @Test
@ -48,7 +49,7 @@ public class GeoJSONShapeSerializerTests {
LineString lineString = GEOMETRY_FACTORY.createLineString( LineString lineString = GEOMETRY_FACTORY.createLineString(
lineCoordinates.toArray(new Coordinate[lineCoordinates.size()])); lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
assertSerializationEquals(expected, new JtsGeometry(lineString)); assertSerializationEquals(expected, new JtsGeometry(lineString, GeoShapeConstants.SPATIAL_CONTEXT, false));
} }
@Test @Test
@ -76,7 +77,7 @@ public class GeoJSONShapeSerializerTests {
shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon polygon = GEOMETRY_FACTORY.createPolygon(shell, null); Polygon polygon = GEOMETRY_FACTORY.createPolygon(shell, null);
assertSerializationEquals(expected, new JtsGeometry(polygon)); assertSerializationEquals(expected, new JtsGeometry(polygon, GeoShapeConstants.SPATIAL_CONTEXT, false));
} }
@Test @Test
@ -121,7 +122,7 @@ public class GeoJSONShapeSerializerTests {
holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon polygon = GEOMETRY_FACTORY.createPolygon(shell, holes); Polygon polygon = GEOMETRY_FACTORY.createPolygon(shell, holes);
assertSerializationEquals(expected, new JtsGeometry(polygon)); assertSerializationEquals(expected, new JtsGeometry(polygon, GeoShapeConstants.SPATIAL_CONTEXT, false));
} }
@Test @Test
@ -140,7 +141,7 @@ public class GeoJSONShapeSerializerTests {
MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPoint( MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPoint(
multiPointCoordinates.toArray(new Coordinate[multiPointCoordinates.size()])); 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 { private void assertSerializationEquals(XContentBuilder expected, Shape shape) throws IOException {

View File

@ -1,8 +1,5 @@
package org.elasticsearch.test.unit.common.lucene.spatial.prefix; 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.Rectangle;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import org.apache.lucene.analysis.KeywordAnalyzer; 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.store.RAMDirectory;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.Version; 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.TermQueryPrefixTreeStrategy;
import org.elasticsearch.common.lucene.spatial.prefix.tree.GeohashPrefixTree; import org.elasticsearch.common.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.elasticsearch.common.lucene.spatial.prefix.tree.QuadPrefixTree; import org.elasticsearch.common.lucene.spatial.prefix.tree.QuadPrefixTree;
@ -37,13 +35,12 @@ import static org.testng.Assert.assertTrue;
*/ */
public class TermQueryPrefixTreeStrategyTests { public class TermQueryPrefixTreeStrategyTests {
private static final SpatialContext SPATIAL_CONTEXT = new JtsSpatialContext(DistanceUnits.KILOMETERS);
// TODO: Randomize the implementation choice // TODO: Randomize the implementation choice
private static final SpatialPrefixTree QUAD_PREFIX_TREE = 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 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); private static final TermQueryPrefixTreeStrategy STRATEGY = new TermQueryPrefixTreeStrategy(new FieldMapper.Names("shape"), GEOHASH_PREFIX_TREE, 0.025);