Upgrade to Spatial4j 0.4.1 and JTS 1.13

Fixes #5279
This commit is contained in:
David Smiley 2014-03-04 11:30:29 -05:00 committed by Simon Willnauer
parent 34077e3121
commit 644fdfc4aa
9 changed files with 105 additions and 54 deletions

View File

@ -146,14 +146,14 @@
<dependency> <dependency>
<groupId>com.spatial4j</groupId> <groupId>com.spatial4j</groupId>
<artifactId>spatial4j</artifactId> <artifactId>spatial4j</artifactId>
<version>0.3</version> <version>0.4.1</version>
<scope>compile</scope> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.vividsolutions</groupId> <groupId>com.vividsolutions</groupId>
<artifactId>jts</artifactId> <artifactId>jts</artifactId>
<version>1.12</version> <version>1.13</version>
<scope>compile</scope> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
<exclusions> <exclusions>

View File

@ -23,10 +23,10 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import com.spatial4j.core.shape.ShapeCollection;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.GeometryFactory;
@ -64,7 +64,7 @@ public abstract class BaseLineStringBuilder<E extends BaseLineStringBuilder<E>>
} else { } else {
geometry = FACTORY.createLineString(coordinates); geometry = FACTORY.createLineString(coordinates);
} }
return new JtsGeometry(geometry, SPATIAL_CONTEXT, !wrapdateline); return jtsGeometry(geometry);
} }
protected static ArrayList<LineString> decompose(GeometryFactory factory, Coordinate[] coordinates, ArrayList<LineString> strings) { protected static ArrayList<LineString> decompose(GeometryFactory factory, Coordinate[] coordinates, ArrayList<LineString> strings) {

View File

@ -27,7 +27,6 @@ import java.util.Iterator;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.GeometryFactory;
@ -149,8 +148,7 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
@Override @Override
public Shape build() { public Shape build() {
Geometry geometry = buildGeometry(FACTORY, wrapdateline); return jtsGeometry(buildGeometry(FACTORY, wrapdateline));
return new JtsGeometry(geometry, SPATIAL_CONTEXT, !wrapdateline);
} }
protected XContentBuilder coordinatesArray(XContentBuilder builder, Params params) throws IOException { protected XContentBuilder coordinatesArray(XContentBuilder builder, Params params) throws IOException {

View File

@ -97,7 +97,7 @@ public class MultiLineStringBuilder extends ShapeBuilder {
} }
geometry = FACTORY.createMultiLineString(lineStrings); geometry = FACTORY.createMultiLineString(lineStrings);
} }
return new JtsGeometry(geometry, SPATIAL_CONTEXT, true); return jtsGeometry(geometry);
} }
public static class InternalLineStringBuilder extends BaseLineStringBuilder<InternalLineStringBuilder> { public static class InternalLineStringBuilder extends BaseLineStringBuilder<InternalLineStringBuilder> {

View File

@ -19,13 +19,15 @@
package org.elasticsearch.common.geo.builders; package org.elasticsearch.common.geo.builders;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry; import com.spatial4j.core.shape.ShapeCollection;
import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.MultiPoint;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MultiPointBuilder extends PointCollection<MultiPointBuilder> { public class MultiPointBuilder extends PointCollection<MultiPointBuilder> {
@ -43,8 +45,13 @@ public class MultiPointBuilder extends PointCollection<MultiPointBuilder> {
@Override @Override
public Shape build() { public Shape build() {
MultiPoint geometry = FACTORY.createMultiPoint(points.toArray(new Coordinate[points.size()])); //Could wrap JtsGeometry but probably slower due to conversions to/from JTS in relate()
return new JtsGeometry(geometry, SPATIAL_CONTEXT, true); //MultiPoint geometry = FACTORY.createMultiPoint(points.toArray(new Coordinate[points.size()]));
List<Point> shapes = new ArrayList<Point>(points.size());
for (Coordinate coord : points) {
shapes.add(SPATIAL_CONTEXT.makePoint(coord.x, coord.y));
}
return new ShapeCollection<Point>(shapes, SPATIAL_CONTEXT);
} }
@Override @Override

View File

@ -21,15 +21,13 @@ package org.elasticsearch.common.geo.builders;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.List;
import com.spatial4j.core.shape.ShapeCollection;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Polygon;
public class MultiPolygonBuilder extends ShapeBuilder { public class MultiPolygonBuilder extends ShapeBuilder {
@ -70,30 +68,24 @@ public class MultiPolygonBuilder extends ShapeBuilder {
@Override @Override
public Shape build() { public Shape build() {
Polygon[] polygons; List<Shape> shapes = new ArrayList<Shape>(this.polygons.size());
if(wrapdateline) { if(wrapdateline) {
ArrayList<Polygon> polygonSet = new ArrayList<Polygon>(this.polygons.size());
for (BasePolygonBuilder<?> polygon : this.polygons) { for (BasePolygonBuilder<?> polygon : this.polygons) {
for(Coordinate[][] part : polygon.coordinates()) { for(Coordinate[][] part : polygon.coordinates()) {
polygonSet.add(PolygonBuilder.polygon(FACTORY, part)); shapes.add(jtsGeometry(PolygonBuilder.polygon(FACTORY, part)));
} }
} }
polygons = polygonSet.toArray(new Polygon[polygonSet.size()]);
} else { } else {
polygons = new Polygon[this.polygons.size()]; for (BasePolygonBuilder<?> polygon : this.polygons) {
Iterator<BasePolygonBuilder<?>> iterator = this.polygons.iterator(); shapes.add(jtsGeometry(polygon.toPolygon(FACTORY)));
for (int i = 0; iterator.hasNext(); i++) {
polygons[i] = iterator.next().toPolygon(FACTORY);
} }
} }
if (shapes.size() == 1)
Geometry geometry = polygons.length == 1 return shapes.get(0);
? polygons[0] else
: FACTORY.createMultiPolygon(polygons); return new ShapeCollection<Shape>(shapes, SPATIAL_CONTEXT);
//note: ShapeCollection is probably faster than a Multi* geom.
return new JtsGeometry(geometry, SPATIAL_CONTEXT, !wrapdateline);
} }
public static class InternalPolygonBuilder extends BasePolygonBuilder<InternalPolygonBuilder> { public static class InternalPolygonBuilder extends BasePolygonBuilder<InternalPolygonBuilder> {

View File

@ -19,6 +19,8 @@
package org.elasticsearch.common.geo.builders; package org.elasticsearch.common.geo.builders;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Geometry;
import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
@ -54,10 +56,21 @@ public abstract class ShapeBuilder implements ToXContent {
} }
public static final double DATELINE = 180; public static final double DATELINE = 180;
public static final GeometryFactory FACTORY = new GeometryFactory(); // TODO how might we use JtsSpatialContextFactory to configure the context (esp. for non-geo)?
public static final JtsSpatialContext SPATIAL_CONTEXT = new JtsSpatialContext(true); public static final JtsSpatialContext SPATIAL_CONTEXT = JtsSpatialContext.GEO;
public static final GeometryFactory FACTORY = SPATIAL_CONTEXT.getGeometryFactory();
protected final boolean wrapdateline = true; /** We're expecting some geometries might cross the dateline. */
protected final boolean wrapdateline = SPATIAL_CONTEXT.isGeo();
/** It's possible that some geometries in a MULTI* shape might overlap. With the possible exception of GeometryCollection,
* this normally isn't allowed.
*/
protected final boolean multiPolygonMayOverlap = false;
/** @see com.spatial4j.core.shape.jts.JtsGeometry#validate() */
protected final boolean autoValidateJtsGeometry = true;
/** @see com.spatial4j.core.shape.jts.JtsGeometry#index() */
protected final boolean autoIndexJtsGeometry = true;//may want to turn off once SpatialStrategy impls do it.
protected ShapeBuilder() { protected ShapeBuilder() {
@ -67,6 +80,16 @@ public abstract class ShapeBuilder implements ToXContent {
return new Coordinate(longitude, latitude); return new Coordinate(longitude, latitude);
} }
protected JtsGeometry jtsGeometry(Geometry geom) {
//dateline180Check is false because ElasticSearch does it's own dateline wrapping
JtsGeometry jtsGeometry = new JtsGeometry(geom, SPATIAL_CONTEXT, false, multiPolygonMayOverlap);
if (autoValidateJtsGeometry)
jtsGeometry.validate();
if (autoIndexJtsGeometry)
jtsGeometry.index();
return jtsGeometry;
}
/** /**
* Create a new point * Create a new point
* *

View File

@ -20,6 +20,7 @@
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.ShapeCollection;
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.vividsolutions.jts.geom.*; import com.vividsolutions.jts.geom.*;
@ -33,15 +34,18 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT;
/** /**
* Tests for {@link GeoJSONShapeParser} * Tests for {@link GeoJSONShapeParser}
*/ */
public class GeoJSONShapeParserTests extends ElasticsearchTestCase { public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
private final static GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); private final static GeometryFactory GEOMETRY_FACTORY = SPATIAL_CONTEXT.getGeometryFactory();
@Test @Test
public void testParse_simplePoint() throws IOException { public void testParse_simplePoint() throws IOException {
@ -50,7 +54,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
.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, ShapeBuilder.SPATIAL_CONTEXT), pointGeoJson); assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
} }
@Test @Test
@ -68,7 +72,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
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, ShapeBuilder.SPATIAL_CONTEXT, false), lineGeoJson); assertGeometryEquals(jtsGeom(expected), lineGeoJson);
} }
@Test @Test
@ -94,7 +98,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null); Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
assertGeometryEquals(new JtsGeometry(expected, ShapeBuilder.SPATIAL_CONTEXT, false), polygonGeoJson); assertGeometryEquals(jtsGeom(expected), polygonGeoJson);
} }
@Test @Test
@ -138,7 +142,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
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, ShapeBuilder.SPATIAL_CONTEXT, false), polygonGeoJson); assertGeometryEquals(jtsGeom(expected), polygonGeoJson);
} }
@Test @Test
@ -150,20 +154,17 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
.endArray() .endArray()
.endObject().string(); .endObject().string();
List<Coordinate> multiPointCoordinates = new ArrayList<Coordinate>(); ShapeCollection expected = shapeCollection(
multiPointCoordinates.add(new Coordinate(100, 0)); SPATIAL_CONTEXT.makePoint(100, 0),
multiPointCoordinates.add(new Coordinate(101, 1)); SPATIAL_CONTEXT.makePoint(101, 1.0));
assertGeometryEquals(expected, multiPointGeoJson);
MultiPoint expected = GEOMETRY_FACTORY.createMultiPoint(
multiPointCoordinates.toArray(new Coordinate[multiPointCoordinates.size()]));
assertGeometryEquals(new JtsGeometry(expected, ShapeBuilder.SPATIAL_CONTEXT, false), multiPointGeoJson);
} }
@Test @Test
public void testParse_multiPolygon() throws IOException { public void testParse_multiPolygon() throws IOException {
String multiPolygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPolygon") String multiPolygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPolygon")
.startArray("coordinates") .startArray("coordinates")
.startArray() .startArray()//first poly (without holes)
.startArray() .startArray()
.startArray().value(102.0).value(2.0).endArray() .startArray().value(102.0).value(2.0).endArray()
.startArray().value(103.0).value(2.0).endArray() .startArray().value(103.0).value(2.0).endArray()
@ -172,7 +173,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
.startArray().value(102.0).value(2.0).endArray() .startArray().value(102.0).value(2.0).endArray()
.endArray() .endArray()
.endArray() .endArray()
.startArray() .startArray()//second poly (with hole)
.startArray() .startArray()
.startArray().value(100.0).value(0.0).endArray() .startArray().value(100.0).value(0.0).endArray()
.startArray().value(101.0).value(0.0).endArray() .startArray().value(101.0).value(0.0).endArray()
@ -180,7 +181,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
.startArray().value(100.0).value(1.0).endArray() .startArray().value(100.0).value(1.0).endArray()
.startArray().value(100.0).value(0.0).endArray() .startArray().value(100.0).value(0.0).endArray()
.endArray() .endArray()
.startArray() .startArray()//hole
.startArray().value(100.2).value(0.8).endArray() .startArray().value(100.2).value(0.8).endArray()
.startArray().value(100.2).value(0.2).endArray() .startArray().value(100.2).value(0.2).endArray()
.startArray().value(100.8).value(0.2).endArray() .startArray().value(100.8).value(0.2).endArray()
@ -221,9 +222,9 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null); Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null);
MultiPolygon expected = GEOMETRY_FACTORY.createMultiPolygon(new Polygon[] {withoutHoles, withHoles}); Shape expected = shapeCollection(withoutHoles, withHoles);
assertGeometryEquals(new JtsGeometry(expected, ShapeBuilder.SPATIAL_CONTEXT, false), multiPolygonGeoJson); assertGeometryEquals(expected, multiPolygonGeoJson);
} }
@Test @Test
@ -244,13 +245,29 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
.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, ShapeBuilder.SPATIAL_CONTEXT), pointGeoJson); assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
} }
private void assertGeometryEquals(Shape expected, String geoJson) throws IOException { private void assertGeometryEquals(Shape expected, String geoJson) throws IOException {
XContentParser parser = JsonXContent.jsonXContent.createParser(geoJson); XContentParser parser = JsonXContent.jsonXContent.createParser(geoJson);
parser.nextToken(); parser.nextToken();
ElasticsearchGeoAssertions.assertEquals(ShapeBuilder.parse(parser).build(), expected); ElasticsearchGeoAssertions.assertEquals(expected, ShapeBuilder.parse(parser).build());
}
private ShapeCollection<Shape> shapeCollection(Shape... shapes) {
return new ShapeCollection<Shape>(Arrays.asList(shapes), SPATIAL_CONTEXT);
}
private ShapeCollection<Shape> shapeCollection(Geometry... geoms) {
List<Shape> shapes = new ArrayList<Shape>(geoms.length);
for (Geometry geom : geoms) {
shapes.add(jtsGeom(geom));
}
return new ShapeCollection<Shape>(shapes, SPATIAL_CONTEXT);
}
private JtsGeometry jtsGeom(Geometry geom) {
return new JtsGeometry(geom, SPATIAL_CONTEXT, false, false);
} }
} }

View File

@ -19,7 +19,9 @@
package org.elasticsearch.test.hamcrest; package org.elasticsearch.test.hamcrest;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
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.vividsolutions.jts.geom.*; import com.vividsolutions.jts.geom.*;
@ -173,6 +175,13 @@ public class ElasticsearchGeoAssertions {
assertEquals(g1.getGeom(), g2.getGeom()); assertEquals(g1.getGeom(), g2.getGeom());
} }
public static void assertEquals(ShapeCollection s1, ShapeCollection s2) {
Assert.assertEquals(s1.size(), s2.size());
for (int i = 0; i < s1.size(); i++) {
assertEquals(s1.get(i), s2.get(i));
}
}
public static void assertEquals(Shape s1, Shape s2) { public static void assertEquals(Shape s1, Shape s2) {
if(s1 instanceof JtsGeometry && s2 instanceof JtsGeometry) { if(s1 instanceof JtsGeometry && s2 instanceof JtsGeometry) {
assertEquals((JtsGeometry) s1, (JtsGeometry) s2); assertEquals((JtsGeometry) s1, (JtsGeometry) s2);
@ -180,8 +189,13 @@ public class ElasticsearchGeoAssertions {
JtsPoint p1 = (JtsPoint) s1; JtsPoint p1 = (JtsPoint) s1;
JtsPoint p2 = (JtsPoint) s2; JtsPoint p2 = (JtsPoint) s2;
Assert.assertEquals(p1, p2); Assert.assertEquals(p1, p2);
} else if (s1 instanceof ShapeCollection && s2 instanceof ShapeCollection) {
assertEquals((ShapeCollection)s1, (ShapeCollection)s2);
} else { } else {
throw new RuntimeException("equality of shape types not supported [" + s1.getClass().getName() + " and " + s2.getClass().getName() + "]"); //We want to know the type of the shape because we test shape equality in a special way...
//... in particular we test that one ring is equivalent to another ring even if the points are rotated or reversed.
throw new RuntimeException(
"equality of shape types not supported [" + s1.getClass().getName() + " and " + s2.getClass().getName() + "]");
} }
} }