Allow PolygonBuilder to create polygons with hole

Closes #2899
This commit is contained in:
Florian Schilling 2013-04-10 12:50:25 +02:00 committed by Simon Willnauer
parent 30f9f278c3
commit ef5b7412e6
8 changed files with 825 additions and 28 deletions

View File

@ -19,6 +19,13 @@
package org.elasticsearch.common.geo;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
@ -26,10 +33,12 @@ 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.vividsolutions.jts.geom.*;
import java.util.ArrayList;
import java.util.List;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
/**
* Utility class for building {@link Shape} instances like {@link Point},
@ -71,6 +80,15 @@ public class ShapeBuilder {
return new PolygonBuilder();
}
/**
* Creates a new {@link MultiPolygonBuilder} to build a MultiPolygon
*
* @return MultiPolygonBuilder instance
*/
public static MultiPolygonBuilder newMultiPolygon() {
return new MultiPolygonBuilder();
}
/**
* Converts the given Shape into the JTS {@link Geometry} representation.
* If the Shape already uses a Geometry, that is returned.
@ -145,12 +163,90 @@ public class ShapeBuilder {
}
}
/**
* Builder for creating a {@link Shape} instance of a MultiPolygon
*/
public static class MultiPolygonBuilder {
private final ArrayList<EmbededPolygonBuilder<MultiPolygonBuilder>> polygons = new ArrayList<EmbededPolygonBuilder<MultiPolygonBuilder>>();
/**
* Add a new polygon to the multipolygon
*
* @return builder for the new polygon
*/
public EmbededPolygonBuilder<MultiPolygonBuilder> polygon() {
EmbededPolygonBuilder<MultiPolygonBuilder> builder = new EmbededPolygonBuilder<MultiPolygonBuilder>(this);
polygons.add(builder);
return builder;
}
public Shape build() {
return new JtsGeometry(toMultiPolygon(), GeoShapeConstants.SPATIAL_CONTEXT, true);
}
public MultiPolygon toMultiPolygon() {
Polygon[] polygons = new Polygon[this.polygons.size()];
for (int i = 0; i<polygons.length; i++) {
polygons[i] = this.polygons.get(i).toPolygon();
}
return GEOMETRY_FACTORY.createMultiPolygon(polygons);
}
public XContentBuilder toXContent(String name, XContentBuilder xcontent) throws IOException {
if(name != null) {
xcontent.startObject(name);
} else {
xcontent.startObject();
}
xcontent.field("type", "multipolygon");
emdedXContent("coordinates", xcontent);
xcontent.endObject();
return xcontent;
}
protected void emdedXContent(String name, XContentBuilder xcontent) throws IOException {
if(name != null) {
xcontent.startArray(name);
} else {
xcontent.startArray();
}
for(EmbededPolygonBuilder<MultiPolygonBuilder> polygon : polygons) {
polygon.emdedXContent(null, xcontent);
}
xcontent.endArray();
}
}
/**
* Builder for creating a {@link Shape} instance of a single Polygon
*/
public static class PolygonBuilder extends EmbededPolygonBuilder<PolygonBuilder> {
private PolygonBuilder() {
super(null);
}
@Override
public PolygonBuilder close() {
super.close();
return this;
}
}
/**
* Builder for creating a {@link Shape} instance of a Polygon
*/
public static class PolygonBuilder {
public static class EmbededPolygonBuilder<E> {
private final List<Point> points = new ArrayList<Point>();
private final E parent;
private final LinearRingBuilder<EmbededPolygonBuilder<E>> ring = new LinearRingBuilder<EmbededPolygonBuilder<E>>(this);
private final ArrayList<LinearRingBuilder<EmbededPolygonBuilder<E>>> holes = new ArrayList<LinearRingBuilder<EmbededPolygonBuilder<E>>>();
private EmbededPolygonBuilder(E parent) {
super();
this.parent = parent;
}
/**
* Adds a point to the Polygon
@ -159,15 +255,25 @@ public class ShapeBuilder {
* @param lat Latitude of the point
* @return this
*/
public PolygonBuilder point(double lon, double lat) {
points.add(new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT));
public EmbededPolygonBuilder<E> point(double lon, double lat) {
ring.point(lon, lat);
return this;
}
/**
* Builds a {@link Shape} instance representing the polygon
* Start creating a new hole within the polygon
* @return a builder for holes
*/
public LinearRingBuilder<EmbededPolygonBuilder<E>> hole() {
LinearRingBuilder<EmbededPolygonBuilder<E>> builder = new LinearRingBuilder<EmbededPolygonBuilder<E>>(this);
this.holes.add(builder);
return builder;
}
/**
* Builds a {@link Shape} instance representing the {@link Polygon}
*
* @return Built polygon
* @return Built LinearRing
*/
public Shape build() {
return new JtsGeometry(toPolygon(), GeoShapeConstants.SPATIAL_CONTEXT, true);
@ -179,13 +285,140 @@ public class ShapeBuilder {
* @return Built polygon
*/
public Polygon toPolygon() {
this.ring.close();
LinearRing ring = this.ring.toLinearRing();
LinearRing[] rings = new LinearRing[holes.size()];
for (int i = 0; i < rings.length; i++) {
rings[i] = this.holes.get(i).toLinearRing();
}
return GEOMETRY_FACTORY.createPolygon(ring, rings);
}
/**
* Close the linestring by copying the first point if necessary
* @return parent object
*/
public E close() {
this.ring.close();
return parent;
}
public XContentBuilder toXContent(String name, XContentBuilder xcontent) throws IOException {
if(name != null) {
xcontent.startObject(name);
} else {
xcontent.startObject();
}
xcontent.field("type", "polygon");
emdedXContent("coordinates", xcontent);
xcontent.endObject();
return xcontent;
}
protected void emdedXContent(String name, XContentBuilder xcontent) throws IOException {
if(name != null) {
xcontent.startArray(name);
} else {
xcontent.startArray();
}
ring.emdedXContent(null, xcontent);
for (LinearRingBuilder<?> ring : holes) {
ring.emdedXContent(null, xcontent);
}
xcontent.endArray();
}
}
/**
* Builder for creating a {@link Shape} instance of a Polygon
*/
public static class LinearRingBuilder<E> {
private final E parent;
private final List<Point> points = new ArrayList<Point>();
private LinearRingBuilder(E parent) {
super();
this.parent = parent;
}
/**
* Adds a point to the Ring
*
* @param lon Longitude of the point
* @param lat Latitude of the point
* @return this
*/
public LinearRingBuilder<E> point(double lon, double lat) {
points.add(new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT));
return this;
}
/**
* Builds a {@link Shape} instance representing the ring
*
* @return Built LinearRing
*/
protected Shape build() {
return new JtsGeometry(toLinearRing(), GeoShapeConstants.SPATIAL_CONTEXT, true);
}
/**
* Creates the raw {@link Polygon}
*
* @return Built LinearRing
*/
protected LinearRing toLinearRing() {
this.close();
Coordinate[] coordinates = new Coordinate[points.size()];
for (int i = 0; i < points.size(); i++) {
for (int i = 0; i < coordinates.length; i++) {
coordinates[i] = new Coordinate(points.get(i).getX(), points.get(i).getY());
}
LinearRing ring = GEOMETRY_FACTORY.createLinearRing(coordinates);
return GEOMETRY_FACTORY.createPolygon(ring, null);
return GEOMETRY_FACTORY.createLinearRing(coordinates);
}
/**
* Close the linestring by copying the first point if necessary
* @return parent object
*/
public E close() {
Point first = points.get(0);
Point last = points.get(points.size()-1);
if(first.getX() != last.getX() || first.getY() != last.getY()) {
points.add(first);
}
if(points.size()<4) {
throw new ElasticSearchIllegalArgumentException("A linear ring is defined by a least four points");
}
return parent;
}
public XContentBuilder toXContent(String name, XContentBuilder xcontent) throws IOException {
if(name != null) {
xcontent.startObject(name);
} else {
xcontent.startObject();
}
xcontent.field("type", "linestring");
emdedXContent("coordinates", xcontent);
xcontent.endObject();
return xcontent;
}
protected void emdedXContent(String name, XContentBuilder xcontent) throws IOException {
if(name != null) {
xcontent.startArray(name);
} else {
xcontent.startArray();
}
for(Point point : points) {
xcontent.startArray().value(point.getY()).value(point.getX()).endArray();
}
xcontent.endArray();
}
}
}

View File

@ -43,6 +43,8 @@ import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.core.AbstractFieldMapper;
import com.spatial4j.core.shape.Shape;
import java.io.IOException;
import java.util.Map;
@ -213,7 +215,8 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
@Override
public void parse(ParseContext context) throws IOException {
try {
Field[] fields = defaultStrategy.createIndexableFields(GeoJSONShapeParser.parse(context.parser()));
Shape shape = GeoJSONShapeParser.parse(context.parser());
Field[] fields = defaultStrategy.createIndexableFields(shape);
if (fields == null || fields.length == 0) {
return;
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.index.query;
import com.spatial4j.core.shape.Shape;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.geo.ShapeRelation;
/**
* A static factory for simple "import static" usage.
@ -358,17 +359,60 @@ public abstract class FilterBuilders {
}
/**
* A filter to filter based on the relationship between a shape and indexed shapes
* A filter based on the relationship of a shape and indexed shapes
*
* @param name The shape field name
* @param shape Shape to use in the filter
* @param relation relation of the shapes
*/
public static GeoShapeFilterBuilder geoShapeFilter(String name, Shape shape, ShapeRelation relation) {
return new GeoShapeFilterBuilder(name, shape, relation);
}
public static GeoShapeFilterBuilder geoShapeFilter(String name, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
return new GeoShapeFilterBuilder(name, indexedShapeId, indexedShapeType, relation);
}
/**
* A filter to filter indexed shapes intersecting with shapes
*
* @param name The shape field name
* @param shape Shape to use in the filter
*/
public static GeoShapeFilterBuilder geoShapeFilter(String name, Shape shape) {
return new GeoShapeFilterBuilder(name, shape);
public static GeoShapeFilterBuilder geoIntersectionFilter(String name, Shape shape) {
return geoShapeFilter(name, shape, ShapeRelation.INTERSECTS);
}
public static GeoShapeFilterBuilder geoShapeFilter(String name, String indexedShapeId, String indexedShapeType) {
return new GeoShapeFilterBuilder(name, indexedShapeId, indexedShapeType);
public static GeoShapeFilterBuilder geoIntersectionFilter(String name, String indexedShapeId, String indexedShapeType) {
return geoShapeFilter(name, indexedShapeId, indexedShapeType, ShapeRelation.INTERSECTS);
}
/**
* A filter to filter indexed shapes that are contained by a shape
*
* @param name The shape field name
* @param shape Shape to use in the filter
*/
public static GeoShapeFilterBuilder geoWithinFilter(String name, Shape shape) {
return geoShapeFilter(name, shape, ShapeRelation.WITHIN);
}
public static GeoShapeFilterBuilder geoWithinFilter(String name, String indexedShapeId, String indexedShapeType) {
return geoShapeFilter(name, indexedShapeId, indexedShapeType, ShapeRelation.WITHIN);
}
/**
* A filter to filter indexed shapes that are not intersection with the query shape
*
* @param name The shape field name
* @param shape Shape to use in the filter
*/
public static GeoShapeFilterBuilder geoDisjointFilter(String name, Shape shape) {
return geoShapeFilter(name, shape, ShapeRelation.DISJOINT);
}
public static GeoShapeFilterBuilder geoDisjointFilter(String name, String indexedShapeId, String indexedShapeType) {
return geoShapeFilter(name, indexedShapeId, indexedShapeType, ShapeRelation.DISJOINT);
}
/**

View File

@ -21,6 +21,7 @@ package org.elasticsearch.index.query;
import com.spatial4j.core.shape.Shape;
import org.elasticsearch.common.geo.GeoJSONShapeSerializer;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -48,6 +49,8 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
private String indexedShapeIndex;
private String indexedShapeFieldName;
private ShapeRelation relation = null;
/**
* Creates a new GeoShapeFilterBuilder whose Filter will be against the
* given field name using the given Shape
@ -56,7 +59,19 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
* @param shape Shape used in the filter
*/
public GeoShapeFilterBuilder(String name, Shape shape) {
this(name, shape, null, null);
this(name, shape, null, null, null);
}
/**
* Creates a new GeoShapeFilterBuilder whose Filter will be against the
* given field name using the given Shape
*
* @param name Name of the field that will be filtered
* @param relation {@link ShapeRelation} of query and indexed shape
* @param shape Shape used in the filter
*/
public GeoShapeFilterBuilder(String name, Shape shape, ShapeRelation relation) {
this(name, shape, null, null, relation);
}
/**
@ -67,14 +82,15 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
* @param indexedShapeId ID of the indexed Shape that will be used in the Filter
* @param indexedShapeType Index type of the indexed Shapes
*/
public GeoShapeFilterBuilder(String name, String indexedShapeId, String indexedShapeType) {
this(name, null, indexedShapeId, indexedShapeType);
public GeoShapeFilterBuilder(String name, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
this(name, null, indexedShapeId, indexedShapeType, relation);
}
private GeoShapeFilterBuilder(String name, Shape shape, String indexedShapeId, String indexedShapeType) {
private GeoShapeFilterBuilder(String name, Shape shape, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
this.name = name;
this.shape = shape;
this.indexedShapeId = indexedShapeId;
this.relation = relation;
this.indexedShapeType = indexedShapeType;
}
@ -145,6 +161,17 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
return this;
}
/**
* Sets the relation of query shape and indexed shape.
*
* @param relation relation of the shapes
* @return this
*/
public GeoShapeFilterBuilder relation(ShapeRelation relation) {
this.relation = relation;
return this;
}
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(GeoShapeFilterParser.NAME);
@ -172,6 +199,10 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
builder.endObject();
}
if(relation != null) {
builder.field("relation", relation.getRelationName());
}
builder.endObject();
if (name != null) {

View File

@ -0,0 +1,486 @@
/*
* 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.test.integration.search.geo;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.geoBoundingBoxFilter;
import static org.elasticsearch.index.query.FilterBuilders.geoDistanceFilter;
import static org.elasticsearch.index.query.QueryBuilders.fieldQuery;
import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.geo.ShapeBuilder;
import org.elasticsearch.common.geo.ShapeBuilder.MultiPolygonBuilder;
import org.elasticsearch.common.geo.ShapeBuilder.PolygonBuilder;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.integration.AbstractNodesTests;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.Node;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
/**
*
*/
public class GeoFilterTests extends AbstractNodesTests {
private Client client;
private boolean intersectSupport;
private boolean disjointSupport;
private boolean withinSupport;
@BeforeClass
public void createNodes() throws Exception {
startNode("server1");
startNode("server2");
intersectSupport = testRelationSupport(SpatialOperation.Intersects);
disjointSupport = testRelationSupport(SpatialOperation.IsDisjointTo);
withinSupport = testRelationSupport(SpatialOperation.IsWithin);
client = getClient();
}
private static byte[] unZipData(String path) throws IOException {
InputStream is = Streams.class.getResourceAsStream(path);
if (is == null) {
throw new FileNotFoundException("Resource [" + path + "] not found in classpath");
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream in = new GZIPInputStream(is);
Streams.copy(in, out);
is.close();
out.close();
return out.toByteArray();
}
@AfterClass
public void closeNodes() {
client.close();
closeAllNodes();
}
protected Client getClient() {
return client("server1");
}
@Test
public void testShapeBuilders() {
try {
// self intersection polygon
ShapeBuilder.newPolygon()
.point(-10, -10)
.point(10, 10)
.point(-10, 10)
.point(10, -10)
.close().build();
assert false : "Self intersection not detected";
} catch (InvalidShapeException e) {}
// polygon with hole
ShapeBuilder.newPolygon()
.point(-10, -10).point(-10, 10).point(10, 10).point(10, -10)
.hole()
.point(-5, -5).point(-5, 5).point(5, 5).point(5, -5)
.close().close().build();
try {
// polygon with overlapping hole
ShapeBuilder.newPolygon()
.point(-10, -10).point(-10, 10).point(10, 10).point(10, -10)
.hole()
.point(-5, -5).point(-5, 11).point(5, 11).point(5, -5)
.close().close().build();
assert false : "Self intersection not detected";
} catch (InvalidShapeException e) {}
try {
// polygon with intersection holes
ShapeBuilder.newPolygon()
.point(-10, -10).point(-10, 10).point(10, 10).point(10, -10)
.hole()
.point(-5, -5).point(-5, 5).point(5, 5).point(5, -5)
.close()
.hole()
.point(-5, -6).point(5, -6).point(5, -4).point(-5, -4)
.close()
.close().build();
assert false : "Intersection of holes not detected";
} catch (InvalidShapeException e) {}
try {
// Common line in polygon
ShapeBuilder.newPolygon()
.point(-10, -10)
.point(-10, 10)
.point(-5, 10)
.point(-5, -5)
.point(-5, 20)
.point(10, 20)
.point(10, -10)
.close().build();
assert false : "Self intersection not detected";
} catch (InvalidShapeException e) {}
// Not specified
// try {
// // two overlapping polygons within a multipolygon
// ShapeBuilder.newMultiPolygon()
// .polygon()
// .point(-10, -10)
// .point(-10, 10)
// .point(10, 10)
// .point(10, -10)
// .close()
// .polygon()
// .point(-5, -5).point(-5, 5).point(5, 5).point(5, -5)
// .close().build();
// assert false : "Polygon intersection not detected";
// } catch (InvalidShapeException e) {}
// Multipolygon: polygon with hole and polygon within the whole
ShapeBuilder.newMultiPolygon()
.polygon()
.point(-10, -10).point(-10, 10).point(10, 10).point(10, -10)
.hole()
.point(-5, -5).point(-5, 5).point(5, 5).point(5, -5)
.close()
.close()
.polygon()
.point(-4, -4).point(-4, 4).point(4, 4).point(4, -4)
.close()
.build();
// Not supported
// try {
// // Multipolygon: polygon with hole and polygon within the hole but overlapping
// ShapeBuilder.newMultiPolygon()
// .polygon()
// .point(-10, -10).point(-10, 10).point(10, 10).point(10, -10)
// .hole()
// .point(-5, -5).point(-5, 5).point(5, 5).point(5, -5)
// .close()
// .close()
// .polygon()
// .point(-4, -4).point(-4, 6).point(4, 6).point(4, -4)
// .close()
// .build();
// assert false : "Polygon intersection not detected";
// } catch (InvalidShapeException e) {}
}
@Test
public void testShapeRelations() throws Exception {
assert intersectSupport: "Intersect relation is not supported";
// assert disjointSupport: "Disjoint relation is not supported";
// assert withinSupport: "within relation is not supported";
assert !disjointSupport: "Disjoint relation is now supported";
assert !withinSupport: "within relation is now supported";
String mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("polygon")
.startObject("properties")
.startObject("area")
.field("type", "geo_shape")
.field("tree", "geohash")
.field("store", true)
.endObject()
.endObject()
.endObject()
.endObject().string();
CreateIndexRequestBuilder mappingRequest = client.admin().indices().prepareCreate("shapes").addMapping("polygon", mapping);
mappingRequest.execute().actionGet();
client.admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet();
// Create a multipolygon with two polygons. The first is an rectangle of size 10x10
// with a hole of size 5x5 equidistant from all sides. This hole in turn contains
// the second polygon of size 4x4 equidistant from all sites
MultiPolygonBuilder polygon = ShapeBuilder.newMultiPolygon()
.polygon()
.point(-10, -10).point(-10, 10).point(10, 10).point(10, -10)
.hole()
.point(-5, -5).point(-5, 5).point(5, 5).point(5, -5)
.close()
.close()
.polygon()
.point(-4, -4).point(-4, 4).point(4, 4).point(4, -4)
.close();
BytesReference data = polygon.toXContent("area", jsonBuilder().startObject()).endObject().bytes();
client.prepareIndex("shapes", "polygon", "1").setSource(data).execute().actionGet();
client.admin().indices().prepareRefresh().execute().actionGet();
// Point in polygon
SearchResponse result = client.prepareSearch()
.setQuery(matchAllQuery())
.setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(3, 3)))
.execute().actionGet();
assertThat(result.getHits().getTotalHits(), equalTo(1L));
assertThat(result.getHits().getAt(0).getId(), equalTo("1"));
// Point in polygon hole
result = client.prepareSearch()
.setQuery(matchAllQuery())
.setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(4.5, 4.5)))
.execute().actionGet();
assertThat(result.getHits().getTotalHits(), equalTo(0L));
// by definition the border of a polygon belongs to the inner
// so the border of a polygons hole also belongs to the inner
// of the polygon NOT the hole
// Point on polygon border
result = client.prepareSearch()
.setQuery(matchAllQuery())
.setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(10.0, 5.0)))
.execute().actionGet();
assertThat(result.getHits().getTotalHits(), equalTo(1L));
assertThat(result.getHits().getAt(0).getId(), equalTo("1"));
// Point on hole border
result = client.prepareSearch()
.setQuery(matchAllQuery())
.setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(5.0, 2.0)))
.execute().actionGet();
assertThat(result.getHits().getTotalHits(), equalTo(1L));
assertThat(result.getHits().getAt(0).getId(), equalTo("1"));
if(disjointSupport) {
// Point not in polygon
result = client.prepareSearch()
.setQuery(matchAllQuery())
.setFilter(FilterBuilders.geoDisjointFilter("area", ShapeBuilder.newPoint(3, 3)))
.execute().actionGet();
assertThat(result.getHits().getTotalHits(), equalTo(0L));
// Point in polygon hole
result = client.prepareSearch()
.setQuery(matchAllQuery())
.setFilter(FilterBuilders.geoDisjointFilter("area", ShapeBuilder.newPoint(4.5, 4.5)))
.execute().actionGet();
assertThat(result.getHits().getTotalHits(), equalTo(1L));
assertThat(result.getHits().getAt(0).getId(), equalTo("1"));
}
// Create a polygon that fills the empty area of the polygon defined above
PolygonBuilder inverse = ShapeBuilder.newPolygon()
.point(-5, -5).point(-5, 5).point(5, 5).point(5, -5)
.hole()
.point(-4, -4).point(-4, 4).point(4, 4).point(4, -4)
.close()
.close();
data = inverse.toXContent("area", jsonBuilder().startObject()).endObject().bytes();
client.prepareIndex("shapes", "polygon", "2").setSource(data).execute().actionGet();
client.admin().indices().prepareRefresh().execute().actionGet();
// re-check point on polygon hole
result = client.prepareSearch()
.setQuery(matchAllQuery())
.setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(4.5, 4.5)))
.execute().actionGet();
assertThat(result.getHits().getTotalHits(), equalTo(1L));
assertThat(result.getHits().getAt(0).getId(), equalTo("2"));
// Create Polygon with hole and common edge
PolygonBuilder builder = ShapeBuilder.newPolygon()
.point(-10, -10).point(-10, 10).point(10, 10).point(10, -10)
.hole()
.point(-5, -5).point(-5, 5).point(10, 5).point(10, -5)
.close()
.close();
if(withinSupport) {
// Polygon WithIn Polygon
builder = ShapeBuilder.newPolygon()
.point(-30, -30).point(-30, 30).point(30, 30).point(30, -30).close();
result = client.prepareSearch()
.setQuery(matchAllQuery())
.setFilter(FilterBuilders.geoWithinFilter("area", builder.build()))
.execute().actionGet();
assertThat(result.getHits().getTotalHits(), equalTo(2L));
}
/* TODO: fix Polygon builder! It is not possible to cross the lats -180 and 180.
* A simple solution is following the path that is currently set up. When
* it's crossing the 180° lat set the new point to the intersection of line-
* segment and longitude and start building a new Polygon on the other side
* of the latitude. When crossing the latitude again continue drawing the
* first polygon. This approach can also applied to the holes because the
* commonline of hole and polygon will not be recognized as intersection.
*/
// // Create a polygon crossing longitude 180.
// builder = ShapeBuilder.newPolygon()
// .point(170, -10).point(180, 10).point(170, -10).point(10, -10)
// .close();
//
// data = builder.toXContent("area", jsonBuilder().startObject()).endObject().bytes();
// client.prepareIndex("shapes", "polygon", "1").setSource(data).execute().actionGet();
// client.admin().indices().prepareRefresh().execute().actionGet();
}
@Test
public void bulktest() throws Exception {
byte[] bulkAction = unZipData("/org/elasticsearch/test/integration/search/geo/gzippedmap.json");
String mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("country")
.startObject("properties")
.startObject("pin")
.field("type", "geo_point")
.field("lat_lon", true)
.field("store", true)
.endObject()
.startObject("location")
.field("type", "geo_shape")
.field("lat_lon", true)
.field("store", true)
.endObject()
.endObject()
.endObject()
.endObject()
.string();
client.admin().indices().prepareCreate("countries").addMapping("country", mapping).execute().actionGet();
BulkResponse bulk = client.prepareBulk().add(bulkAction, 0, bulkAction.length, false, null, null).execute().actionGet();
for(BulkItemResponse item : bulk.getItems()) {
assert !item.isFailed(): "unable to index data";
}
client.admin().indices().prepareRefresh().execute().actionGet();
String key = "DE";
SearchResponse searchResponse = client.prepareSearch()
.setQuery(fieldQuery("_id", key))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(1L));
for (SearchHit hit : searchResponse.getHits()) {
assertThat(hit.getId(), equalTo(key));
}
SearchResponse world = client.prepareSearch().addField("pin").setQuery(
filteredQuery(
matchAllQuery(),
geoBoundingBoxFilter("pin")
.topLeft(90, -179.99999)
.bottomRight(-90, 179.99999))
).execute().actionGet();
assertThat(world.getHits().totalHits(), equalTo(246L));
SearchResponse distance = client.prepareSearch().addField("pin").setQuery(
filteredQuery(
matchAllQuery(),
geoDistanceFilter("pin").distance("425km").point(51.11, 9.851)
)).execute().actionGet();
assertThat(distance.getHits().totalHits(), equalTo(5L));
GeoPoint point = new GeoPoint();
for (SearchHit hit : distance.getHits()) {
String name = hit.getId();
point.resetFromString(hit.fields().get("pin").getValue().toString());
double dist = distance(point.getLat(), point.getLon(), 51.11, 9.851);
assertThat("distance to '" + name + "'", dist, lessThanOrEqualTo(425000d));
assertThat(name, anyOf(equalTo("CZ"), equalTo("DE"), equalTo("BE"), equalTo("NL"), equalTo("LU")));
if(key.equals(name)) {
assertThat(dist, equalTo(0d));
}
}
}
public static double distance(double lat1, double lon1, double lat2, double lon2) {
return GeoUtils.EARTH_SEMI_MAJOR_AXIS * DistanceUtils.distHaversineRAD(
DistanceUtils.toRadians(lat1),
DistanceUtils.toRadians(lon1),
DistanceUtils.toRadians(lat2),
DistanceUtils.toRadians(lon2)
);
}
protected static boolean testRelationSupport(SpatialOperation relation) {
try {
GeohashPrefixTree tree = new GeohashPrefixTree(SpatialContext.GEO, 3);
RecursivePrefixTreeStrategy strategy = new RecursivePrefixTreeStrategy(tree, "area");
Shape shape = SpatialContext.GEO.makePoint(0, 0);
SpatialArgs args = new SpatialArgs(relation, shape);
strategy.makeFilter(args);
return true;
} catch (UnsupportedSpatialOperation e) {
return false;
}
}
}

View File

@ -33,7 +33,7 @@ import org.testng.annotations.Test;
import static org.elasticsearch.common.geo.ShapeBuilder.newRectangle;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.geoShapeFilter;
import static org.elasticsearch.index.query.FilterBuilders.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@ -94,7 +94,7 @@ public class GeoShapeIntegrationTests extends AbstractNodesTests {
SearchResponse searchResponse = client.prepareSearch()
.setQuery(filteredQuery(matchAllQuery(),
geoShapeFilter("location", shape)))
geoIntersectionFilter("location", shape)))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(1l));
@ -144,7 +144,7 @@ public class GeoShapeIntegrationTests extends AbstractNodesTests {
// used the bottom-level optimization in SpatialPrefixTree#recursiveGetNodes.
SearchResponse searchResponse = client.prepareSearch()
.setQuery(filteredQuery(matchAllQuery(),
geoShapeFilter("location", query)))
geoIntersectionFilter("location", query)))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(1l));
@ -188,7 +188,7 @@ public class GeoShapeIntegrationTests extends AbstractNodesTests {
SearchResponse searchResponse = client.prepareSearch("test")
.setQuery(filteredQuery(matchAllQuery(),
geoShapeFilter("location", "Big_Rectangle", "shape_type")))
geoIntersectionFilter("location", "Big_Rectangle", "shape_type")))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(1l));

View File

@ -56,7 +56,7 @@ public class ShapeBuilderTests {
.point(45, 30)
.point(45, -30)
.point(-45, -30)
.point(-45, 30);
.close();
Shape polygon = polygonBuilder.build();
Geometry polygonGeometry = ShapeBuilder.toJTSGeometry(polygon);