> components) {
+ // Assign Hole to related components
+ // To find the new component the hole belongs to all intersections of the
+ // polygon edges with a vertical line are calculated. This vertical line
+ // is an arbitrary point of the hole. The polygon edge next to this point
+ // is part of the polygon the hole belongs to.
+ if (debugEnabled()) {
+ LOGGER.debug("Holes: " + Arrays.toString(holes));
+ }
+ for (int i = 0; i < numHoles; i++) {
+ final Edge current = holes[i];
+ final int intersections = intersections(current.coordinate.x, edges);
+ final int pos = Arrays.binarySearch(edges, 0, intersections, current, INTERSECTION_ORDER);
+ assert pos < 0 : "illegal state: two edges cross the datum at the same position";
+ final int index = -(pos+2);
+ final int component = -edges[index].component - numHoles - 1;
+
+ if(debugEnabled()) {
+ LOGGER.debug("\tposition ("+index+") of edge "+current+": " + edges[index]);
+ LOGGER.debug("\tComponent: " + component);
+ LOGGER.debug("\tHole intersections ("+current.coordinate.x+"): " + Arrays.toString(edges));
+ }
+
+ components.get(component).add(points[i]);
+ }
+ }
+
+ private static int merge(Edge[] intersections, int offset, int length, Edge[] holes, int numHoles) {
+ // Intersections appear pairwise. On the first edge the inner of
+ // of the polygon is entered. On the second edge the outer face
+ // is entered. Other kinds of intersections are discard by the
+ // intersection function
+
+ for (int i = 0; i < length; i += 2) {
+ Edge e1 = intersections[offset + i + 0];
+ Edge e2 = intersections[offset + i + 1];
+
+ // If two segments are connected maybe a hole must be deleted
+ // Since Edges of components appear pairwise we need to check
+ // the second edge only (the first edge is either polygon or
+ // already handled)
+ if (e2.component > 0) {
+ //TODO: Check if we could save the set null step
+ numHoles--;
+ holes[e2.component-1] = holes[numHoles];
+ holes[numHoles] = null;
+ }
+ connect(e1, e2);
+ }
+ return numHoles;
+ }
+
+ private static void connect(Edge in, Edge out) {
+ assert in != null && out != null;
+ assert in != out;
+ // Connecting two Edges by inserting the point at
+ // dateline intersection and connect these by adding
+ // two edges between this points. One per direction
+ if(in.intersect != in.next.coordinate) {
+ // NOTE: the order of the object creation is crucial here! Don't change it!
+ // first edge has no point on dateline
+ Edge e1 = new Edge(in.intersect, in.next);
+
+ if(out.intersect != out.next.coordinate) {
+ // second edge has no point on dateline
+ Edge e2 = new Edge(out.intersect, out.next);
+ in.next = new Edge(in.intersect, e2, in.intersect);
+ } else {
+ // second edge intersects with dateline
+ in.next = new Edge(in.intersect, out.next, in.intersect);
+ }
+ out.next = new Edge(out.intersect, e1, out.intersect);
+ } else {
+ // first edge intersects with dateline
+ Edge e2 = new Edge(out.intersect, in.next, out.intersect);
+
+ if(out.intersect != out.next.coordinate) {
+ // second edge has no point on dateline
+ Edge e1 = new Edge(out.intersect, out.next);
+ in.next = new Edge(in.intersect, e1, in.intersect);
+
+ } else {
+ // second edge intersects with dateline
+ in.next = new Edge(in.intersect, out.next, in.intersect);
+ }
+ out.next = e2;
+ }
+ }
+
+ private static int createEdges(int component, boolean direction, BaseLineStringBuilder> line, Edge[] edges, int offset) {
+ Coordinate[] points = line.coordinates(false); // last point is repeated
+ Edge.ring(component, direction, points, 0, edges, offset, points.length-1);
+ return points.length-1;
+ }
+
+ public static class Ring extends BaseLineStringBuilder> {
+
+ private final P parent;
+
+ protected Ring(P parent) {
+ this(parent, new ArrayList());
+ }
+
+ protected Ring(P parent, ArrayList points) {
+ super(points);
+ this.parent = parent;
+ }
+
+ public P close() {
+ Coordinate start = points.get(0);
+ Coordinate end = points.get(points.size()-1);
+ if(start.x != end.x || start.y != end.y) {
+ points.add(start);
+ }
+ return parent;
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java
new file mode 100644
index 00000000000..ffcb6741c97
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java
@@ -0,0 +1,120 @@
+/*
+ * 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.builders;
+
+import java.io.IOException;
+
+import org.elasticsearch.common.unit.DistanceUnit;
+import org.elasticsearch.common.unit.DistanceUnit.Distance;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import com.spatial4j.core.shape.Circle;
+import com.vividsolutions.jts.geom.Coordinate;
+
+public class CircleBuilder extends ShapeBuilder {
+
+ public static final String FIELD_RADIUS = "radius";
+ public static final GeoShapeType TYPE = GeoShapeType.CIRCLE;
+
+ private DistanceUnit unit;
+ private double radius;
+ private Coordinate center;
+
+ /**
+ * Set the center of the circle
+ *
+ * @param center coordinate of the circles center
+ * @return this
+ */
+ public CircleBuilder center(Coordinate center) {
+ this.center = center;
+ return this;
+ }
+
+ /**
+ * set the center of the circle
+ * @param lon longitude of the center
+ * @param lat latitude of the center
+ * @return this
+ */
+ public CircleBuilder center(double lon, double lat) {
+ return center(new Coordinate(lon, lat));
+ }
+
+ /**
+ * Set the radius of the circle. The String value will be parsed by {@link DistanceUnit}
+ * @param radius Value and unit of the circle combined in a string
+ * @return this
+ */
+ public CircleBuilder radius(String radius) {
+ return radius(DistanceUnit.Distance.parseDistance(radius, DistanceUnit.METERS));
+ }
+
+ /**
+ * Set the radius of the circle
+ * @param radius radius of the circle (see {@link DistanceUnit.Distance})
+ * @return this
+ */
+ public CircleBuilder radius(Distance radius) {
+ return radius(radius.value, radius.unit);
+ }
+
+ /**
+ * Set the radius of the circle
+ * @param radius value of the circles radius
+ * @param unit unit name of the radius value (see {@link DistanceUnit})
+ * @return this
+ */
+ public CircleBuilder radius(double radius, String unit) {
+ return radius(radius, DistanceUnit.fromString(unit));
+ }
+
+ /**
+ * Set the radius of the circle
+ * @param radius value of the circles radius
+ * @param unit unit of the radius value (see {@link DistanceUnit})
+ * @return this
+ */
+ public CircleBuilder radius(double radius, DistanceUnit unit) {
+ this.unit = unit;
+ this.radius = radius;
+ return this;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(FIELD_TYPE, TYPE.shapename);
+ builder.field(FIELD_RADIUS, unit.toString(radius));
+ builder.field(FIELD_COORDINATES);
+ toXContent(builder, center);
+ return builder.endObject();
+ }
+
+ @Override
+ public Circle build() {
+ return SPATIAL_CONTEXT.makeCircle(center.x, center.y, 180 * radius / unit.getEarthCircumference());
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return TYPE;
+ }
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java
new file mode 100644
index 00000000000..3bff5dd537f
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java
@@ -0,0 +1,76 @@
+/*
+ * 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.builders;
+
+import java.io.IOException;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import com.spatial4j.core.shape.Rectangle;
+import com.vividsolutions.jts.geom.Coordinate;
+
+public class EnvelopeBuilder extends ShapeBuilder {
+
+ public static final GeoShapeType TYPE = GeoShapeType.ENVELOPE;
+
+ protected Coordinate northEast;
+ protected Coordinate southWest;
+
+ public EnvelopeBuilder topLeft(Coordinate northEast) {
+ this.northEast = northEast;
+ return this;
+ }
+
+ public EnvelopeBuilder topLeft(double longitude, double latitude) {
+ return topLeft(coordinate(longitude, latitude));
+ }
+
+ public EnvelopeBuilder bottomRight(Coordinate southWest) {
+ this.southWest = southWest;
+ return this;
+ }
+
+ public EnvelopeBuilder bottomRight(double longitude, double latitude) {
+ return bottomRight(coordinate(longitude, latitude));
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(FIELD_TYPE, TYPE.shapename);
+ builder.startArray(FIELD_COORDINATES);
+ toXContent(builder, northEast);
+ toXContent(builder, southWest);
+ builder.endArray();
+ return builder.endObject();
+ }
+
+ @Override
+ public Rectangle build() {
+ return SPATIAL_CONTEXT.makeRectangle(
+ northEast.x, southWest.x,
+ southWest.y, northEast.y);
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return TYPE;
+ }
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java
new file mode 100644
index 00000000000..afc69ced56e
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.builders;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+public class LineStringBuilder extends BaseLineStringBuilder {
+
+ public static final GeoShapeType TYPE = GeoShapeType.LINESTRING;
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(FIELD_TYPE, TYPE.shapename);
+ builder.field(FIELD_COORDINATES);
+ coordinatesToXcontent(builder, false);
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return TYPE;
+ }
+
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java
new file mode 100644
index 00000000000..9e3ccd45f85
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java
@@ -0,0 +1,125 @@
+/*
+ * 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.builders;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.jts.JtsGeometry;
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.LineString;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class MultiLineStringBuilder extends ShapeBuilder {
+
+ public static final GeoShapeType TYPE = GeoShapeType.MULTILINESTRING;
+
+ private final ArrayList> lines = new ArrayList>();
+
+ public InternalLineStringBuilder linestring() {
+ InternalLineStringBuilder line = new InternalLineStringBuilder(this);
+ this.lines.add(line);
+ return line;
+ }
+
+ public MultiLineStringBuilder linestring(BaseLineStringBuilder> line) {
+ this.lines.add(line);
+ return this;
+ }
+
+ public Coordinate[][] coordinates() {
+ Coordinate[][] result = new Coordinate[lines.size()][];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = lines.get(i).coordinates(false);
+ }
+ return result;
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return TYPE;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(FIELD_TYPE, TYPE.shapename);
+ builder.field(FIELD_COORDINATES);
+ builder.startArray();
+ for(BaseLineStringBuilder> line : lines) {
+ line.coordinatesToXcontent(builder, false);
+ }
+ builder.endArray();
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public Shape build() {
+ final Geometry geometry;
+ if(wrapdateline) {
+ ArrayList parts = new ArrayList();
+ for (BaseLineStringBuilder> line : lines) {
+ BaseLineStringBuilder.decompose(FACTORY, line.coordinates(false), parts);
+ }
+ if(parts.size() == 1) {
+ geometry = parts.get(0);
+ } else {
+ LineString[] lineStrings = parts.toArray(new LineString[parts.size()]);
+ geometry = FACTORY.createMultiLineString(lineStrings);
+ }
+ } else {
+ LineString[] lineStrings = new LineString[lines.size()];
+ Iterator> iterator = lines.iterator();
+ for (int i = 0; iterator.hasNext(); i++) {
+ lineStrings[i] = FACTORY.createLineString(iterator.next().coordinates(false));
+ }
+ geometry = FACTORY.createMultiLineString(lineStrings);
+ }
+ return new JtsGeometry(geometry, SPATIAL_CONTEXT, true);
+ }
+
+ public static class InternalLineStringBuilder extends BaseLineStringBuilder {
+
+ private final MultiLineStringBuilder collection;
+
+ public InternalLineStringBuilder(MultiLineStringBuilder collection) {
+ super();
+ this.collection = collection;
+ }
+
+ public MultiLineStringBuilder end() {
+ return collection;
+ }
+
+ public Coordinate[] coordinates() {
+ return super.coordinates(false);
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java
new file mode 100644
index 00000000000..5f43e953407
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java
@@ -0,0 +1,54 @@
+/*
+ * 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.builders;
+
+import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.jts.JtsGeometry;
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.MultiPoint;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+public class MultiPointBuilder extends PointCollection {
+
+ public static final GeoShapeType TYPE = GeoShapeType.MULTIPOINT;
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(FIELD_TYPE, TYPE.shapename);
+ builder.field(FIELD_COORDINATES);
+ super.coordinatesToXcontent(builder, false);
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public Shape build() {
+ MultiPoint geometry = FACTORY.createMultiPoint(points.toArray(new Coordinate[points.size()]));
+ return new JtsGeometry(geometry, SPATIAL_CONTEXT, true);
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return TYPE;
+ }
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java
new file mode 100644
index 00000000000..0a7852a9ae1
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java
@@ -0,0 +1,115 @@
+/*
+ * 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.builders;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.jts.JtsGeometry;
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.Polygon;
+
+public class MultiPolygonBuilder extends ShapeBuilder {
+
+ public static final GeoShapeType TYPE = GeoShapeType.MULTIPOLYGON;
+
+ protected final ArrayList> polygons = new ArrayList>();
+
+ public MultiPolygonBuilder polygon(BasePolygonBuilder> polygon) {
+ this.polygons.add(polygon);
+ return this;
+ }
+
+ public InternalPolygonBuilder polygon() {
+ InternalPolygonBuilder polygon = new InternalPolygonBuilder(this);
+ this.polygon(polygon);
+ return polygon;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(FIELD_TYPE, TYPE.shapename);
+ builder.startArray(FIELD_COORDINATES);
+ for(BasePolygonBuilder> polygon : polygons) {
+ builder.startArray();
+ polygon.coordinatesArray(builder, params);
+ builder.endArray();
+ }
+ builder.endArray();
+ return builder.endObject();
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return TYPE;
+ }
+
+ @Override
+ public Shape build() {
+
+ Polygon[] polygons;
+
+ if(wrapdateline) {
+ ArrayList polygonSet = new ArrayList(this.polygons.size());
+ for (BasePolygonBuilder> polygon : this.polygons) {
+ for(Coordinate[][] part : polygon.coordinates()) {
+ polygonSet.add(PolygonBuilder.polygon(FACTORY, part));
+ }
+ }
+
+ polygons = polygonSet.toArray(new Polygon[polygonSet.size()]);
+ } else {
+ polygons = new Polygon[this.polygons.size()];
+ Iterator> iterator = this.polygons.iterator();
+ for (int i = 0; iterator.hasNext(); i++) {
+ polygons[i] = iterator.next().toPolygon(FACTORY);
+ }
+ }
+
+ Geometry geometry = polygons.length == 1
+ ? polygons[0]
+ : FACTORY.createMultiPolygon(polygons);
+
+ return new JtsGeometry(geometry, SPATIAL_CONTEXT, !wrapdateline);
+ }
+
+ public static class InternalPolygonBuilder extends BasePolygonBuilder {
+
+ private final MultiPolygonBuilder collection;
+
+ private InternalPolygonBuilder(MultiPolygonBuilder collection) {
+ super();
+ this.collection = collection;
+ this.shell = new Ring(this);
+ }
+
+ @Override
+ public MultiPolygonBuilder close() {
+ super.close();
+ return collection;
+ }
+ }
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java
new file mode 100644
index 00000000000..f37edd3934a
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java
@@ -0,0 +1,66 @@
+/*
+ * 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.builders;
+
+import java.io.IOException;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import com.spatial4j.core.shape.Point;
+import com.vividsolutions.jts.geom.Coordinate;
+
+public class PointBuilder extends ShapeBuilder {
+
+ public static final GeoShapeType TYPE = GeoShapeType.POINT;
+
+ private Coordinate coordinate;
+
+ public PointBuilder coordinate(Coordinate coordinate) {
+ this.coordinate = coordinate;
+ return this;
+ }
+
+ public double longitude() {
+ return coordinate.x;
+ }
+
+ public double latitude() {
+ return coordinate.y;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(FIELD_TYPE, TYPE.shapename);
+ builder.field(FIELD_COORDINATES);
+ toXContent(builder, coordinate);
+ return builder.endObject();
+ }
+
+ @Override
+ public Point build() {
+ return SPATIAL_CONTEXT.makePoint(coordinate.x, coordinate.y);
+ }
+
+ @Override
+ public GeoShapeType type() {
+ return TYPE;
+ }
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/PointCollection.java b/src/main/java/org/elasticsearch/common/geo/builders/PointCollection.java
new file mode 100644
index 00000000000..7eb02e41ecf
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/PointCollection.java
@@ -0,0 +1,129 @@
+/*
+ * 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.builders;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import com.vividsolutions.jts.geom.Coordinate;
+
+/**
+ * The {@link PointCollection} is an abstract base implementation for all GeoShapes. It simply handles a set of points.
+ */
+public abstract class PointCollection> extends ShapeBuilder {
+
+ protected final ArrayList points;
+
+ protected PointCollection() {
+ this(new ArrayList());
+ }
+
+ protected PointCollection(ArrayList points) {
+ this.points = points;
+ }
+
+ @SuppressWarnings("unchecked")
+ private E thisRef() {
+ return (E)this;
+ }
+
+ /**
+ * Add a new point to the collection
+ * @param longitude longitude of the coordinate
+ * @param latitude latitude of the coordinate
+ * @return this
+ */
+ public E point(double longitude, double latitude) {
+ return this.point(coordinate(longitude, latitude));
+ }
+
+ /**
+ * Add a new point to the collection
+ * @param coordinate coordinate of the point
+ * @return this
+ */
+ public E point(Coordinate coordinate) {
+ this.points.add(coordinate);
+ return thisRef();
+ }
+
+ /**
+ * Add a array of points to the collection
+ *
+ * @param coordinates array of {@link Coordinate}s to add
+ * @return this
+ */
+ public E points(Coordinate...coordinates) {
+ return this.points(Arrays.asList(coordinates));
+ }
+
+ /**
+ * Add a collection of points to the collection
+ *
+ * @param coordinates array of {@link Coordinate}s to add
+ * @return this
+ */
+ public E points(Collection extends Coordinate> coordinates) {
+ this.points.addAll(coordinates);
+ return thisRef();
+ }
+
+ /**
+ * Copy all points to a new Array
+ *
+ * @param closed if set to true the first point of the array is repeated as last element
+ * @return Array of coordinates
+ */
+ protected Coordinate[] coordinates(boolean closed) {
+ Coordinate[] result = points.toArray(new Coordinate[points.size() + (closed?1:0)]);
+ if(closed) {
+ result[result.length-1] = result[0];
+ }
+ return result;
+ }
+
+ /**
+ * builds an array of coordinates to a {@link XContentBuilder}
+ *
+ * @param builder builder to use
+ * @param closed repeat the first point at the end of the array if it's not already defines as last element of the array
+ * @return the builder
+ * @throws IOException
+ */
+ protected XContentBuilder coordinatesToXcontent(XContentBuilder builder, boolean closed) throws IOException {
+ builder.startArray();
+ for(Coordinate point : points) {
+ toXContent(builder, point);
+ }
+ if(closed) {
+ Coordinate start = points.get(0);
+ Coordinate end = points.get(points.size()-1);
+ if(start.x != end.x || start.y != end.y) {
+ toXContent(builder, points.get(0));
+ }
+ }
+ builder.endArray();
+ return builder;
+ }
+}
diff --git a/src/main/java/org/elasticsearch/common/geo/GeoShapeConstants.java b/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java
similarity index 60%
rename from src/main/java/org/elasticsearch/common/geo/GeoShapeConstants.java
rename to src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java
index 355ffa4af50..5d53c4dfc2a 100644
--- a/src/main/java/org/elasticsearch/common/geo/GeoShapeConstants.java
+++ b/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java
@@ -17,14 +17,26 @@
* under the License.
*/
-package org.elasticsearch.common.geo;
+package org.elasticsearch.common.geo.builders;
-import com.spatial4j.core.context.jts.JtsSpatialContext;
+import java.util.ArrayList;
-/**
- * Common constants through the GeoShape codebase
- */
-public interface GeoShapeConstants {
+import com.vividsolutions.jts.geom.Coordinate;
- public static final JtsSpatialContext SPATIAL_CONTEXT = new JtsSpatialContext(true);
+public class PolygonBuilder extends BasePolygonBuilder {
+
+ public PolygonBuilder() {
+ this(new ArrayList());
+ }
+
+ protected PolygonBuilder(ArrayList points) {
+ super();
+ this.shell = new Ring(this, points);
+ }
+
+ @Override
+ public PolygonBuilder close() {
+ super.close();
+ return this;
+ }
}
diff --git a/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java
new file mode 100644
index 00000000000..012d6fd8606
--- /dev/null
+++ b/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java
@@ -0,0 +1,628 @@
+/*
+ * 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.builders;
+
+import java.io.IOException;
+import java.util.*;
+
+import org.elasticsearch.ElasticSearchIllegalArgumentException;
+import org.elasticsearch.ElasticSearchParseException;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.unit.DistanceUnit;
+import org.elasticsearch.common.unit.DistanceUnit.Distance;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+
+import com.spatial4j.core.context.jts.JtsSpatialContext;
+import com.spatial4j.core.shape.Shape;
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.GeometryFactory;
+
+public abstract class ShapeBuilder implements ToXContent {
+
+ protected static final ESLogger LOGGER = ESLoggerFactory.getLogger(ShapeBuilder.class.getName());
+
+ private static final boolean DEBUG;
+ static {
+ // if asserts are enabled we run the debug statements even if they are not logged
+ // to prevent exceptions only present if debug enabled
+ boolean debug = false;
+ assert debug = true;
+ DEBUG = debug;
+ }
+
+ public static final double DATELINE = 180;
+ public static final GeometryFactory FACTORY = new GeometryFactory();
+ public static final JtsSpatialContext SPATIAL_CONTEXT = new JtsSpatialContext(true);
+
+ protected final boolean wrapdateline = true;
+
+ protected ShapeBuilder() {
+
+ }
+
+ protected static Coordinate coordinate(double longitude, double latitude) {
+ return new Coordinate(longitude, latitude);
+ }
+
+ /**
+ * Create a new point
+ *
+ * @param longitude longitude of the point
+ * @param latitude latitude of the point
+ * @return a new {@link PointBuilder}
+ */
+ public static PointBuilder newPoint(double longitude, double latitude) {
+ return newPoint(new Coordinate(longitude, latitude));
+ }
+
+ /**
+ * Create a new {@link PointBuilder} from a {@link Coordinate}
+ * @param coordinate coordinate defining the position of the point
+ * @return a new {@link PointBuilder}
+ */
+ public static PointBuilder newPoint(Coordinate coordinate) {
+ return new PointBuilder().coordinate(coordinate);
+ }
+
+ /**
+ * Create a new set of points
+ * @return new {@link MultiPointBuilder}
+ */
+ public static MultiPointBuilder newMultiPoint() {
+ return new MultiPointBuilder();
+ }
+
+ /**
+ * Create a new lineString
+ * @return a new {@link LineStringBuilder}
+ */
+ public static LineStringBuilder newLineString() {
+ return new LineStringBuilder();
+ }
+
+ /**
+ * Create a new Collection of lineStrings
+ * @return a new {@link MultiLineStringBuilder}
+ */
+ public static MultiLineStringBuilder newMultiLinestring() {
+ return new MultiLineStringBuilder();
+ }
+
+ /**
+ * Create a new Polygon
+ * @return a new {@link PointBuilder}
+ */
+ public static PolygonBuilder newPolygon() {
+ return new PolygonBuilder();
+ }
+
+ /**
+ * Create a new Collection of polygons
+ * @return a new {@link MultiPolygonBuilder}
+ */
+ public static MultiPolygonBuilder newMultiPolygon() {
+ return new MultiPolygonBuilder();
+ }
+
+ /**
+ * create a new Circle
+ * @return a new {@link CircleBuilder}
+ */
+ public static CircleBuilder newCircleBuilder() {
+ return new CircleBuilder();
+ }
+
+ /**
+ * create a new rectangle
+ * @return a new {@link EnvelopeBuilder}
+ */
+ public static EnvelopeBuilder newEnvelope() {
+ return new EnvelopeBuilder();
+ }
+
+ @Override
+ public String toString() {
+ try {
+ XContentBuilder xcontent = JsonXContent.contentBuilder();
+ return toXContent(xcontent, EMPTY_PARAMS).prettyPrint().string();
+ } catch (IOException e) {
+ return super.toString();
+ }
+ }
+
+ /**
+ * Create a new Shape from this builder. Since calling this method could change the
+ * defined shape. (by inserting new coordinates or change the position of points)
+ * the builder looses its validity. So this method should only be called once on a builder
+ * @return new {@link Shape} defined by the builder
+ */
+ public abstract Shape build();
+
+ /**
+ * Recursive method which parses the arrays of coordinates used to define
+ * Shapes
+ *
+ * @param parser
+ * Parser that will be read from
+ * @return CoordinateNode representing the start of the coordinate tree
+ * @throws IOException
+ * Thrown if an error occurs while reading from the
+ * XContentParser
+ */
+ private static CoordinateNode parseCoordinates(XContentParser parser) throws IOException {
+ XContentParser.Token token = parser.nextToken();
+
+ // Base case
+ if (token != XContentParser.Token.START_ARRAY) {
+ double lon = parser.doubleValue();
+ token = parser.nextToken();
+ double lat = parser.doubleValue();
+ token = parser.nextToken();
+ return new CoordinateNode(new Coordinate(lon, lat));
+ }
+
+ List nodes = new ArrayList();
+ while (token != XContentParser.Token.END_ARRAY) {
+ nodes.add(parseCoordinates(parser));
+ token = parser.nextToken();
+ }
+
+ return new CoordinateNode(nodes);
+ }
+
+ /**
+ * Create a new {@link ShapeBuilder} from {@link XContent}
+ * @param parser parser to read the GeoShape from
+ * @return {@link ShapeBuilder} read from the parser or null
+ * if the parsers current token has been
+ * @throws IOException if the input could not be read
+ */
+ public static ShapeBuilder parse(XContentParser parser) throws IOException {
+ return GeoShapeType.parse(parser);
+ }
+
+ protected static XContentBuilder toXContent(XContentBuilder builder, Coordinate coordinate) throws IOException {
+ return builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
+ }
+
+ protected static Coordinate shift(Coordinate coordinate, double dateline) {
+ if (dateline == 0) {
+ return coordinate;
+ } else {
+ return new Coordinate(-2 * dateline + coordinate.x, coordinate.y);
+ }
+ }
+
+ /**
+ * get the shapes type
+ * @return type of the shape
+ */
+ public abstract GeoShapeType type();
+
+ /**
+ * Calculate the intersection of a line segment and a vertical dateline.
+ *
+ * @param p1
+ * start-point of the line segment
+ * @param p2
+ * end-point of the line segment
+ * @param dateline
+ * x-coordinate of the vertical dateline
+ * @return position of the intersection in the open range (0..1] if the line
+ * segment intersects with the line segment. Otherwise this method
+ * returns {@link Double#NaN}
+ */
+ protected static final double intersection(Coordinate p1, Coordinate p2, double dateline) {
+ if (p1.x == p2.x) {
+ return Double.NaN;
+ } else {
+ final double t = (dateline - p1.x) / (p2.x - p1.x);
+ if (t > 1 || t <= 0) {
+ return Double.NaN;
+ } else {
+ return t;
+ }
+ }
+ }
+
+ /**
+ * Calculate all intersections of line segments and a vertical line. The
+ * Array of edges will be ordered asc by the y-coordinate of the
+ * intersections of edges.
+ *
+ * @param dateline
+ * x-coordinate of the dateline
+ * @param edges
+ * set of edges that may intersect with the dateline
+ * @return number of intersecting edges
+ */
+ protected static int intersections(double dateline, Edge[] edges) {
+ int numIntersections = 0;
+ assert !Double.isNaN(dateline);
+ for (int i = 0; i < edges.length; i++) {
+ Coordinate p1 = edges[i].coordinate;
+ Coordinate p2 = edges[i].next.coordinate;
+ assert !Double.isNaN(p2.x) && !Double.isNaN(p1.x);
+ edges[i].intersect = IntersectionOrder.SENTINEL;
+
+ double position = intersection(p1, p2, dateline);
+ if (!Double.isNaN(position)) {
+ if (position == 1) {
+ if (Double.compare(p1.x, dateline) == Double.compare(edges[i].next.next.coordinate.x, dateline)) {
+ // Ignore the ear
+ continue;
+ } else if (p2.x == dateline) {
+ // Ignore Linesegment on dateline
+ continue;
+ }
+ }
+ edges[i].intersection(position);
+ numIntersections++;
+ }
+ }
+ Arrays.sort(edges, INTERSECTION_ORDER);
+ return numIntersections;
+ }
+
+ /**
+ * Node used to represent a tree of coordinates.
+ *
+ * Can either be a leaf node consisting of a Coordinate, or a parent with
+ * children
+ */
+ protected static class CoordinateNode implements ToXContent {
+
+ protected final Coordinate coordinate;
+ protected final List children;
+
+ /**
+ * Creates a new leaf CoordinateNode
+ *
+ * @param coordinate
+ * Coordinate for the Node
+ */
+ protected CoordinateNode(Coordinate coordinate) {
+ this.coordinate = coordinate;
+ this.children = null;
+ }
+
+ /**
+ * Creates a new parent CoordinateNode
+ *
+ * @param children
+ * Children of the Node
+ */
+ protected CoordinateNode(List children) {
+ this.children = children;
+ this.coordinate = null;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ if (children == null) {
+ builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
+ } else {
+ builder.startArray();
+ for (CoordinateNode child : children) {
+ child.toXContent(builder, params);
+ }
+ builder.endArray();
+ }
+ return builder;
+ }
+ }
+
+ /**
+ * This helper class implements a linked list for {@link Coordinate}. It contains
+ * fields for a dateline intersection and component id
+ */
+ protected static final class Edge {
+ Coordinate coordinate; // coordinate of the start point
+ Edge next; // next segment
+ Coordinate intersect; // potential intersection with dateline
+ int component = -1; // id of the component this edge belongs to
+
+ protected Edge(Coordinate coordinate, Edge next, Coordinate intersection) {
+ this.coordinate = coordinate;
+ this.next = next;
+ this.intersect = intersection;
+ if (next != null) {
+ this.component = next.component;
+ }
+ }
+
+ protected Edge(Coordinate coordinate, Edge next) {
+ this(coordinate, next, IntersectionOrder.SENTINEL);
+ }
+
+ private static final int top(Coordinate[] points, int offset, int length) {
+ int top = 0; // we start at 1 here since top points to 0
+ for (int i = 1; i < length; i++) {
+ if (points[offset + i].y < points[offset + top].y) {
+ top = i;
+ } else if (points[offset + i].y == points[offset + top].y) {
+ if (points[offset + i].x < points[offset + top].x) {
+ top = i;
+ }
+ }
+ }
+ return top;
+ }
+
+ /**
+ * Concatenate a set of points to a polygon
+ *
+ * @param component
+ * component id of the polygon
+ * @param direction
+ * direction of the ring
+ * @param points
+ * list of points to concatenate
+ * @param pointOffset
+ * index of the first point
+ * @param edges
+ * Array of edges to write the result to
+ * @param edgeOffset
+ * index of the first edge in the result
+ * @param length
+ * number of points to use
+ * @return the edges creates
+ */
+ private static Edge[] concat(int component, boolean direction, Coordinate[] points, final int pointOffset, Edge[] edges, final int edgeOffset,
+ int length) {
+ assert edges.length >= length+edgeOffset;
+ assert points.length >= length+pointOffset;
+ edges[edgeOffset] = new Edge(points[pointOffset], null);
+ for (int i = 1; i < length; i++) {
+ if (direction) {
+ edges[edgeOffset + i] = new Edge(points[pointOffset + i], edges[edgeOffset + i - 1]);
+ edges[edgeOffset + i].component = component;
+ } else {
+ edges[edgeOffset + i - 1].next = edges[edgeOffset + i] = new Edge(points[pointOffset + i], null);
+ edges[edgeOffset + i - 1].component = component;
+ }
+ }
+
+ if (direction) {
+ edges[edgeOffset].next = edges[edgeOffset + length - 1];
+ edges[edgeOffset].component = component;
+ } else {
+ edges[edgeOffset + length - 1].next = edges[edgeOffset];
+ edges[edgeOffset + length - 1].component = component;
+ }
+
+ return edges;
+ }
+
+ /**
+ * Create a connected list of a list of coordinates
+ *
+ * @param points
+ * array of point
+ * @param offset
+ * index of the first point
+ * @param length
+ * number of points
+ * @return Array of edges
+ */
+ protected static Edge[] ring(int component, boolean direction, Coordinate[] points, int offset, Edge[] edges, int toffset,
+ int length) {
+ // calculate the direction of the points:
+ // find the point a the top of the set and check its
+ // neighbors orientation. So direction is equivalent
+ // to clockwise/counterclockwise
+ final int top = top(points, offset, length);
+ final int prev = (offset + ((top + length - 1) % length));
+ final int next = (offset + ((top + 1) % length));
+ final boolean orientation = points[offset + prev].x > points[offset + next].x;
+ return concat(component, direction ^ orientation, points, offset, edges, toffset, length);
+ }
+
+ /**
+ * Set the intersection of this line segment to the given position
+ *
+ * @param position
+ * position of the intersection [0..1]
+ * @return the {@link Coordinate} of the intersection
+ */
+ protected Coordinate intersection(double position) {
+ return intersect = position(coordinate, next.coordinate, position);
+ }
+
+ public static Coordinate position(Coordinate p1, Coordinate p2, double position) {
+ if (position == 0) {
+ return p1;
+ } else if (position == 1) {
+ return p2;
+ } else {
+ final double x = p1.x + position * (p2.x - p1.x);
+ final double y = p1.y + position * (p2.y - p1.y);
+ return new Coordinate(x, y);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Edge[Component=" + component + "; start=" + coordinate + " " + "; intersection=" + intersect + "]";
+ }
+ }
+
+ protected static final IntersectionOrder INTERSECTION_ORDER = new IntersectionOrder();
+
+ private static final class IntersectionOrder implements Comparator {
+ private static final Coordinate SENTINEL = new Coordinate(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+
+ @Override
+ public int compare(Edge o1, Edge o2) {
+ return Double.compare(o1.intersect.y, o2.intersect.y);
+ }
+
+ }
+
+ public static final String FIELD_TYPE = "type";
+ public static final String FIELD_COORDINATES = "coordinates";
+
+ protected static final boolean debugEnabled() {
+ return LOGGER.isDebugEnabled() || DEBUG;
+ }
+
+ /**
+ * Enumeration that lists all {@link GeoShapeType}s that can be handled
+ */
+ public static enum GeoShapeType {
+ POINT("point"),
+ MULTIPOINT("multipoint"),
+ LINESTRING("linestring"),
+ MULTILINESTRING("multilinestring"),
+ POLYGON("polygon"),
+ MULTIPOLYGON("multipolygon"),
+ ENVELOPE("envelope"),
+ CIRCLE("circle");
+
+ protected final String shapename;
+
+ private GeoShapeType(String shapename) {
+ this.shapename = shapename;
+ }
+
+ public static GeoShapeType forName(String geoshapename) {
+ String typename = geoshapename.toLowerCase(Locale.ROOT);
+ for (GeoShapeType type : values()) {
+ if(type.shapename.equals(typename)) {
+ return type;
+ }
+ }
+ throw new ElasticSearchIllegalArgumentException("unknown geo_shape ["+geoshapename+"]");
+ }
+
+ public static ShapeBuilder parse(XContentParser parser) throws IOException {
+ if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
+ return null;
+ } else if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
+ throw new ElasticSearchParseException("Shape must be an object consisting of type and coordinates");
+ }
+
+ GeoShapeType shapeType = null;
+ Distance radius = null;
+ CoordinateNode node = null;
+
+ XContentParser.Token token;
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ String fieldName = parser.currentName();
+
+ if (FIELD_TYPE.equals(fieldName)) {
+ parser.nextToken();
+ shapeType = GeoShapeType.forName(parser.text());
+ } else if (FIELD_COORDINATES.equals(fieldName)) {
+ parser.nextToken();
+ node = parseCoordinates(parser);
+ } else if (CircleBuilder.FIELD_RADIUS.equals(fieldName)) {
+ parser.nextToken();
+ radius = Distance.parseDistance(parser.text(), DistanceUnit.METERS);
+ } else {
+ parser.nextToken();
+ parser.skipChildren();
+ }
+ }
+ }
+
+ if (shapeType == null) {
+ throw new ElasticSearchParseException("Shape type not included");
+ } else if (node == null) {
+ throw new ElasticSearchParseException("Coordinates not included");
+ } else if (radius != null && GeoShapeType.CIRCLE != shapeType) {
+ throw new ElasticSearchParseException("Field [" + CircleBuilder.FIELD_RADIUS + "] is supported for [" + CircleBuilder.TYPE
+ + "] only");
+ }
+
+ switch (shapeType) {
+ case POINT: return parsePoint(node);
+ case MULTIPOINT: return parseMultiPoint(node);
+ case LINESTRING: return parseLineString(node);
+ case MULTILINESTRING: return parseMultiLine(node);
+ case POLYGON: return parsePolygon(node);
+ case MULTIPOLYGON: return parseMultiPolygon(node);
+ case CIRCLE: return parseCircle(node, radius);
+ case ENVELOPE: return parseEnvelope(node);
+ default:
+ throw new ElasticSearchParseException("Shape type [" + shapeType + "] not included");
+ }
+ }
+
+ protected static PointBuilder parsePoint(CoordinateNode node) {
+ return newPoint(node.coordinate);
+ }
+
+ protected static CircleBuilder parseCircle(CoordinateNode coordinates, Distance radius) {
+ return newCircleBuilder().center(coordinates.coordinate).radius(radius);
+ }
+
+ protected static EnvelopeBuilder parseEnvelope(CoordinateNode coordinates) {
+ return newEnvelope().topLeft(coordinates.children.get(0).coordinate).bottomRight(coordinates.children.get(1).coordinate);
+ }
+
+ protected static MultiPointBuilder parseMultiPoint(CoordinateNode coordinates) {
+ MultiPointBuilder points = new MultiPointBuilder();
+ for (CoordinateNode node : coordinates.children) {
+ points.point(node.coordinate);
+ }
+ return points;
+ }
+
+ protected static LineStringBuilder parseLineString(CoordinateNode coordinates) {
+ LineStringBuilder line = newLineString();
+ for (CoordinateNode node : coordinates.children) {
+ line.point(node.coordinate);
+ }
+ return line;
+ }
+
+ protected static MultiLineStringBuilder parseMultiLine(CoordinateNode coordinates) {
+ MultiLineStringBuilder multiline = newMultiLinestring();
+ for (CoordinateNode node : coordinates.children) {
+ multiline.linestring(parseLineString(node));
+ }
+ return multiline;
+ }
+
+ protected static PolygonBuilder parsePolygon(CoordinateNode coordinates) {
+ LineStringBuilder shell = parseLineString(coordinates.children.get(0));
+ PolygonBuilder polygon = new PolygonBuilder(shell.points);
+ for (int i = 1; i < coordinates.children.size(); i++) {
+ polygon.hole(parseLineString(coordinates.children.get(i)));
+ }
+ return polygon;
+ }
+
+ protected static MultiPolygonBuilder parseMultiPolygon(CoordinateNode coordinates) {
+ MultiPolygonBuilder polygons = newMultiPolygon();
+ for (CoordinateNode node : coordinates.children) {
+ polygons.polygon(parsePolygon(node));
+ }
+ return polygons;
+ }
+ }
+}
diff --git a/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java
index 42778106b5e..34ed79984df 100644
--- a/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java
+++ b/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java
@@ -29,10 +29,9 @@ import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.Strings;
-import org.elasticsearch.common.geo.GeoJSONShapeParser;
-import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.geo.SpatialStrategy;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.codec.postingsformat.PostingsFormatProvider;
@@ -43,8 +42,6 @@ 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;
@@ -143,9 +140,9 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper {
final FieldMapper.Names names = buildNames(context);
if (Names.TREE_GEOHASH.equals(tree)) {
- prefixTree = new GeohashPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, getLevels(treeLevels, precisionInMeters, Defaults.GEOHASH_LEVELS, true));
+ prefixTree = new GeohashPrefixTree(ShapeBuilder.SPATIAL_CONTEXT, getLevels(treeLevels, precisionInMeters, Defaults.GEOHASH_LEVELS, true));
} else if (Names.TREE_QUADTREE.equals(tree)) {
- prefixTree = new QuadPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, getLevels(treeLevels, precisionInMeters, Defaults.QUADTREE_LEVELS, false));
+ prefixTree = new QuadPrefixTree(ShapeBuilder.SPATIAL_CONTEXT, getLevels(treeLevels, precisionInMeters, Defaults.QUADTREE_LEVELS, false));
} else {
throw new ElasticSearchIllegalArgumentException("Unknown prefix tree type [" + tree + "]");
}
@@ -215,8 +212,8 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper {
@Override
public void parse(ParseContext context) throws IOException {
try {
- Shape shape = GeoJSONShapeParser.parse(context.parser());
- Field[] fields = defaultStrategy.createIndexableFields(shape);
+ ShapeBuilder shape = ShapeBuilder.parse(context.parser());
+ Field[] fields = defaultStrategy.createIndexableFields(shape.build());
if (fields == null || fields.length == 0) {
return;
}
diff --git a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java
index 6a8e197ed2b..186f676d8cf 100644
--- a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java
+++ b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java
@@ -19,10 +19,10 @@
package org.elasticsearch.index.query;
-import com.spatial4j.core.shape.Shape;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.ShapeRelation;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
/**
* A static factory for simple "import static" usage.
@@ -414,7 +414,7 @@ public abstract class FilterBuilders {
* @param shape Shape to use in the filter
* @param relation relation of the shapes
*/
- public static GeoShapeFilterBuilder geoShapeFilter(String name, Shape shape, ShapeRelation relation) {
+ public static GeoShapeFilterBuilder geoShapeFilter(String name, ShapeBuilder shape, ShapeRelation relation) {
return new GeoShapeFilterBuilder(name, shape, relation);
}
@@ -428,7 +428,7 @@ public abstract class FilterBuilders {
* @param name The shape field name
* @param shape Shape to use in the filter
*/
- public static GeoShapeFilterBuilder geoIntersectionFilter(String name, Shape shape) {
+ public static GeoShapeFilterBuilder geoIntersectionFilter(String name, ShapeBuilder shape) {
return geoShapeFilter(name, shape, ShapeRelation.INTERSECTS);
}
@@ -442,7 +442,7 @@ public abstract class FilterBuilders {
* @param name The shape field name
* @param shape Shape to use in the filter
*/
- public static GeoShapeFilterBuilder geoWithinFilter(String name, Shape shape) {
+ public static GeoShapeFilterBuilder geoWithinFilter(String name, ShapeBuilder shape) {
return geoShapeFilter(name, shape, ShapeRelation.WITHIN);
}
@@ -456,7 +456,7 @@ public abstract class FilterBuilders {
* @param name The shape field name
* @param shape Shape to use in the filter
*/
- public static GeoShapeFilterBuilder geoDisjointFilter(String name, Shape shape) {
+ public static GeoShapeFilterBuilder geoDisjointFilter(String name, ShapeBuilder shape) {
return geoShapeFilter(name, shape, ShapeRelation.DISJOINT);
}
diff --git a/src/main/java/org/elasticsearch/index/query/GeoShapeFilterBuilder.java b/src/main/java/org/elasticsearch/index/query/GeoShapeFilterBuilder.java
index e0807624446..2b57bb6f34d 100644
--- a/src/main/java/org/elasticsearch/index/query/GeoShapeFilterBuilder.java
+++ b/src/main/java/org/elasticsearch/index/query/GeoShapeFilterBuilder.java
@@ -19,14 +19,13 @@
package org.elasticsearch.index.query;
-import com.spatial4j.core.shape.Shape;
-import org.elasticsearch.common.geo.GeoJSONShapeSerializer;
+import java.io.IOException;
+
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.xcontent.XContentBuilder;
-import java.io.IOException;
-
/**
* {@link FilterBuilder} that builds a GeoShape Filter
*/
@@ -34,7 +33,7 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
private final String name;
- private final Shape shape;
+ private final ShapeBuilder shape;
private SpatialStrategy strategy = null;
@@ -58,7 +57,7 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
* @param name Name of the field that will be filtered
* @param shape Shape used in the filter
*/
- public GeoShapeFilterBuilder(String name, Shape shape) {
+ public GeoShapeFilterBuilder(String name, ShapeBuilder shape) {
this(name, shape, null, null, null);
}
@@ -70,7 +69,7 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
* @param relation {@link ShapeRelation} of query and indexed shape
* @param shape Shape used in the filter
*/
- public GeoShapeFilterBuilder(String name, Shape shape, ShapeRelation relation) {
+ public GeoShapeFilterBuilder(String name, ShapeBuilder shape, ShapeRelation relation) {
this(name, shape, null, null, relation);
}
@@ -86,7 +85,7 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
this(name, null, indexedShapeId, indexedShapeType, relation);
}
- private GeoShapeFilterBuilder(String name, Shape shape, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
+ private GeoShapeFilterBuilder(String name, ShapeBuilder shape, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
this.name = name;
this.shape = shape;
this.indexedShapeId = indexedShapeId;
@@ -183,9 +182,7 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder {
}
if (shape != null) {
- builder.startObject("shape");
- GeoJSONShapeSerializer.serialize(shape, builder);
- builder.endObject();
+ builder.field("shape", shape);
} else {
builder.startObject("indexed_shape")
.field("id", indexedShapeId)
diff --git a/src/main/java/org/elasticsearch/index/query/GeoShapeFilterParser.java b/src/main/java/org/elasticsearch/index/query/GeoShapeFilterParser.java
index 210368f4fe9..cc76c003162 100644
--- a/src/main/java/org/elasticsearch/index/query/GeoShapeFilterParser.java
+++ b/src/main/java/org/elasticsearch/index/query/GeoShapeFilterParser.java
@@ -22,8 +22,8 @@ package org.elasticsearch.index.query;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.search.Filter;
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
-import org.elasticsearch.common.geo.GeoJSONShapeParser;
import org.elasticsearch.common.geo.ShapeRelation;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.xcontent.XContentParser;
@@ -80,7 +80,7 @@ public class GeoShapeFilterParser implements FilterParser {
String fieldName = null;
ShapeRelation shapeRelation = ShapeRelation.INTERSECTS;
String strategyName = null;
- Shape shape = null;
+ ShapeBuilder shape = null;
boolean cache = false;
CacheKeyFilter.Key cacheKey = null;
String filterName = null;
@@ -105,7 +105,7 @@ public class GeoShapeFilterParser implements FilterParser {
token = parser.nextToken();
if ("shape".equals(currentFieldName)) {
- shape = GeoJSONShapeParser.parse(parser);
+ shape = ShapeBuilder.parse(parser);
} else if ("relation".equals(currentFieldName)) {
shapeRelation = ShapeRelation.getRelationByName(parser.text());
if (shapeRelation == null) {
diff --git a/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java
index a47294d4548..c8b60ded8a2 100644
--- a/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java
+++ b/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java
@@ -19,13 +19,12 @@
package org.elasticsearch.index.query;
-import com.spatial4j.core.shape.Shape;
-import org.elasticsearch.common.geo.GeoJSONShapeSerializer;
-import org.elasticsearch.common.geo.SpatialStrategy;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-
import java.io.IOException;
+import org.elasticsearch.common.geo.SpatialStrategy;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
/**
* {@link QueryBuilder} that builds a GeoShape Query
*/
@@ -35,7 +34,7 @@ public class GeoShapeQueryBuilder extends BaseQueryBuilder implements BoostableQ
private SpatialStrategy strategy = null;
- private final Shape shape;
+ private final ShapeBuilder shape;
private float boost = -1;
@@ -52,7 +51,7 @@ public class GeoShapeQueryBuilder extends BaseQueryBuilder implements BoostableQ
* @param name Name of the field that will be queried
* @param shape Shape used in the query
*/
- public GeoShapeQueryBuilder(String name, Shape shape) {
+ public GeoShapeQueryBuilder(String name, ShapeBuilder shape) {
this(name, shape, null, null);
}
@@ -68,7 +67,7 @@ public class GeoShapeQueryBuilder extends BaseQueryBuilder implements BoostableQ
this(name, null, indexedShapeId, indexedShapeType);
}
- private GeoShapeQueryBuilder(String name, Shape shape, String indexedShapeId, String indexedShapeType) {
+ private GeoShapeQueryBuilder(String name, ShapeBuilder shape, String indexedShapeId, String indexedShapeType) {
this.name = name;
this.shape = shape;
this.indexedShapeId = indexedShapeId;
@@ -129,9 +128,7 @@ public class GeoShapeQueryBuilder extends BaseQueryBuilder implements BoostableQ
}
if (shape != null) {
- builder.startObject("shape");
- GeoJSONShapeSerializer.serialize(shape, builder);
- builder.endObject();
+ builder.field("shape", shape);
} else {
builder.startObject("indexed_shape")
.field("id", indexedShapeId)
diff --git a/src/main/java/org/elasticsearch/index/query/GeoShapeQueryParser.java b/src/main/java/org/elasticsearch/index/query/GeoShapeQueryParser.java
index 25ec5643d18..b9735fbd281 100644
--- a/src/main/java/org/elasticsearch/index/query/GeoShapeQueryParser.java
+++ b/src/main/java/org/elasticsearch/index/query/GeoShapeQueryParser.java
@@ -19,7 +19,6 @@
package org.elasticsearch.index.query;
-import com.spatial4j.core.shape.Shape;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
@@ -27,8 +26,8 @@ import org.apache.lucene.spatial.query.SpatialOperation;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
-import org.elasticsearch.common.geo.GeoJSONShapeParser;
import org.elasticsearch.common.geo.ShapeRelation;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.FieldMapper;
@@ -61,7 +60,7 @@ public class GeoShapeQueryParser implements QueryParser {
String fieldName = null;
ShapeRelation shapeRelation = ShapeRelation.INTERSECTS;
String strategyName = null;
- Shape shape = null;
+ ShapeBuilder shape = null;
String id = null;
String type = null;
@@ -83,7 +82,7 @@ public class GeoShapeQueryParser implements QueryParser {
currentFieldName = parser.currentName();
token = parser.nextToken();
if ("shape".equals(currentFieldName)) {
- shape = GeoJSONShapeParser.parse(parser);
+ shape = ShapeBuilder.parse(parser);
} else if ("strategy".equals(currentFieldName)) {
strategyName = parser.text();
} else if ("relation".equals(currentFieldName)) {
@@ -156,14 +155,14 @@ public class GeoShapeQueryParser implements QueryParser {
this.fetchService = fetchService;
}
- public static SpatialArgs getArgs(Shape shape, ShapeRelation relation) {
+ public static SpatialArgs getArgs(ShapeBuilder shape, ShapeRelation relation) {
switch(relation) {
case DISJOINT:
- return new SpatialArgs(SpatialOperation.IsDisjointTo, shape);
+ return new SpatialArgs(SpatialOperation.IsDisjointTo, shape.build());
case INTERSECTS:
- return new SpatialArgs(SpatialOperation.Intersects, shape);
+ return new SpatialArgs(SpatialOperation.Intersects, shape.build());
case WITHIN:
- return new SpatialArgs(SpatialOperation.IsWithin, shape);
+ return new SpatialArgs(SpatialOperation.IsWithin, shape.build());
default:
throw new ElasticSearchIllegalArgumentException("");
diff --git a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java
index 8413e3ef1ae..56d1abc2e06 100644
--- a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java
+++ b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java
@@ -19,11 +19,11 @@
package org.elasticsearch.index.query;
-import com.spatial4j.core.shape.Shape;
-import org.elasticsearch.common.Nullable;
-
import java.util.Collection;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
+
/**
* A static factory for simple "import static" usage.
*/
@@ -693,7 +693,7 @@ public abstract class QueryBuilders {
* @param name The field name
* @param values The terms
*/
- public static TermsQueryBuilder termsQuery(String name, Collection values) {
+ public static TermsQueryBuilder termsQuery(String name, Collection> values) {
return new TermsQueryBuilder(name, values);
}
@@ -763,7 +763,7 @@ public abstract class QueryBuilders {
* @param name The field name
* @param values The terms
*/
- public static TermsQueryBuilder inQuery(String name, Collection values) {
+ public static TermsQueryBuilder inQuery(String name, Collection> values) {
return new TermsQueryBuilder(name, values);
}
@@ -796,7 +796,7 @@ public abstract class QueryBuilders {
* @param name The shape field name
* @param shape Shape to use in the Query
*/
- public static GeoShapeQueryBuilder geoShapeQuery(String name, Shape shape) {
+ public static GeoShapeQueryBuilder geoShapeQuery(String name, ShapeBuilder shape) {
return new GeoShapeQueryBuilder(name, shape);
}
diff --git a/src/main/java/org/elasticsearch/index/search/shape/ShapeFetchService.java b/src/main/java/org/elasticsearch/index/search/shape/ShapeFetchService.java
index 58a3c192d61..e0a5993febd 100644
--- a/src/main/java/org/elasticsearch/index/search/shape/ShapeFetchService.java
+++ b/src/main/java/org/elasticsearch/index/search/shape/ShapeFetchService.java
@@ -19,14 +19,13 @@
package org.elasticsearch.index.search.shape;
-import com.spatial4j.core.shape.Shape;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.ElasticSearchIllegalStateException;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.component.AbstractComponent;
-import org.elasticsearch.common.geo.GeoJSONShapeParser;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentHelper;
@@ -57,7 +56,7 @@ public class ShapeFetchService extends AbstractComponent {
* @return Shape with the given ID
* @throws IOException Can be thrown while parsing the Shape Document and extracting the Shape
*/
- public Shape fetch(String id, String type, String index, String shapeField) throws IOException {
+ public ShapeBuilder fetch(String id, String type, String index, String shapeField) throws IOException {
GetResponse response = client.get(new GetRequest(index, type, id).preference("_local").operationThreaded(false)).actionGet();
if (!response.isExists()) {
throw new ElasticSearchIllegalArgumentException("Shape with ID [" + id + "] in type [" + type + "] not found");
@@ -71,7 +70,7 @@ public class ShapeFetchService extends AbstractComponent {
if (currentToken == XContentParser.Token.FIELD_NAME) {
if (shapeField.equals(parser.currentName())) {
parser.nextToken();
- return GeoJSONShapeParser.parse(parser);
+ return ShapeBuilder.parse(parser);
} else {
parser.nextToken();
parser.skipChildren();
diff --git a/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java b/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java
new file mode 100644
index 00000000000..967eba4b14e
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java
@@ -0,0 +1,205 @@
+/*
+ * 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.hamcrest;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.jts.JtsGeometry;
+import com.spatial4j.core.shape.jts.JtsPoint;
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.MultiLineString;
+import com.vividsolutions.jts.geom.MultiPoint;
+import com.vividsolutions.jts.geom.MultiPolygon;
+import com.vividsolutions.jts.geom.Polygon;
+
+public class ElasticsearchGeoAssertions {
+
+ private static int top(Coordinate...points) {
+ int top = 0;
+ for (int i = 1; i < points.length; i++) {
+ if(points[i].y < points[top].y) {
+ top = i;
+ } else if(points[i].y == points[top].y) {
+ if(points[i].x <= points[top].x) {
+ top = i;
+ }
+ }
+ }
+ return top;
+ }
+
+ private static int prev(int top, Coordinate...points) {
+ for (int i = 1; i < points.length; i++) {
+ int p = (top + points.length - i) % points.length;
+ if((points[p].x != points[top].x) || (points[p].y != points[top].y)) {
+ return p;
+ }
+ }
+ return -1;
+ }
+
+ private static int next(int top, Coordinate...points) {
+ for (int i = 1; i < points.length; i++) {
+ int n = (top + i) % points.length;
+ if((points[n].x != points[top].x) || (points[n].y != points[top].y)) {
+ return n;
+ }
+ }
+ return -1;
+ }
+
+ private static Coordinate[] fixedOrderedRing(List coordinates, boolean direction) {
+ return fixedOrderedRing(coordinates.toArray(new Coordinate[coordinates.size()]), direction);
+ }
+
+ private static Coordinate[] fixedOrderedRing(Coordinate[] points, boolean direction) {
+
+ final int top = top(points);
+ final int next = next(top, points);
+ final int prev = prev(top, points);
+ final boolean orientation = points[next].x < points[prev].x;
+
+ if(orientation != direction) {
+ List asList = Arrays.asList(points);
+ Collections.reverse(asList);
+ return fixedOrderedRing(asList, direction);
+ } else {
+ if(top>0) {
+ Coordinate[] aligned = new Coordinate[points.length];
+ System.arraycopy(points, top, aligned, 0, points.length-top-1);
+ System.arraycopy(points, 0, aligned, points.length-top-1, top);
+ aligned[aligned.length-1] = aligned[0];
+ return aligned;
+ } else {
+ return points;
+ }
+ }
+
+ }
+
+ public static void assertEquals(Coordinate c1, Coordinate c2) {
+ assert (c1.x == c2.x && c1.y == c2.y): "expected coordinate " + c1 + " but found " + c2;
+ }
+
+ private static boolean isRing(Coordinate[] c) {
+ return (c[0].x == c[c.length-1].x) && (c[0].y == c[c.length-1].y);
+ }
+
+ public static void assertEquals(Coordinate[] c1, Coordinate[] c2) {
+ assert (c1.length == c1.length) : "expected " + c1.length + " coordinates but found " + c2.length;
+
+ if(isRing(c1) && isRing(c2)) {
+ c1 = fixedOrderedRing(c1, true);
+ c2 = fixedOrderedRing(c2, true);
+ }
+
+ for (int i = 0; i < c2.length; i++) {
+ assertEquals(c1[i], c2[i]);
+ }
+ }
+
+ public static void assertEquals(LineString l1, LineString l2) {
+ assertEquals(l1.getCoordinates(), l2.getCoordinates());
+ }
+
+ public static void assertEquals(Polygon p1, Polygon p2) {
+ assert (p1.getNumInteriorRing() == p2.getNumInteriorRing()) : "expect " + p1.getNumInteriorRing() + " interior ring but found " + p2.getNumInteriorRing();
+
+ assertEquals(p1.getExteriorRing(), p2.getExteriorRing());
+
+ // TODO: This test do not check all permutations of linestrings. So the test
+ // fails if the holes of the polygons are not ordered the same way
+ for (int i = 0; i < p1.getNumInteriorRing(); i++) {
+ assertEquals(p1.getInteriorRingN(i), p2.getInteriorRingN(i));
+ }
+ }
+
+ public static void assertEquals(MultiPolygon p1, MultiPolygon p2) {
+ assert p1.getNumGeometries() == p2.getNumGeometries(): "expected " + p1.getNumGeometries() + " geometries but found " + p2.getNumGeometries();
+
+ // TODO: This test do not check all permutations. So the Test fails
+ // if the inner polygons are not ordered the same way in both Multipolygons
+ for (int i = 0; i < p1.getNumGeometries(); i++) {
+ Geometry a = p1.getGeometryN(i);
+ Geometry b = p2.getGeometryN(i);
+ assertEquals(a, b);
+ }
+ }
+
+ public static void assertEquals(Geometry s1, Geometry s2) {
+ if(s1 instanceof LineString && s2 instanceof LineString) {
+ assertEquals((LineString) s1, (LineString) s2);
+
+ } else if (s1 instanceof Polygon && s2 instanceof Polygon) {
+ assertEquals((Polygon) s1, (Polygon) s2);
+
+ } else if (s1 instanceof MultiPoint && s2 instanceof MultiPoint) {
+ assert s1.equals(s2): "Expected " + s1 + " but found " + s2;
+
+ } else if (s1 instanceof MultiPolygon && s2 instanceof MultiPolygon) {
+ assertEquals((MultiPolygon) s1, (MultiPolygon) s2);
+
+ } else {
+ throw new RuntimeException("equality of shape types not supported [" + s1.getClass().getName() + " and " + s2.getClass().getName() + "]");
+ }
+ }
+
+ public static void assertEquals(JtsGeometry g1, JtsGeometry g2) {
+ assertEquals(g1.getGeom(), g2.getGeom());
+ }
+
+ public static void assertEquals(Shape s1, Shape s2) {
+ if(s1 instanceof JtsGeometry && s2 instanceof JtsGeometry) {
+ assertEquals((JtsGeometry) s1, (JtsGeometry) s2);
+ } else if(s1 instanceof JtsPoint && s2 instanceof JtsPoint) {
+ JtsPoint p1 = (JtsPoint) s1;
+ JtsPoint p2 = (JtsPoint) s2;
+ assert p1.equals(p1): "expected " + p1 + " but found " + p2;
+ } else {
+ throw new RuntimeException("equality of shape types not supported [" + s1.getClass().getName() + " and " + s2.getClass().getName() + "]");
+ }
+ }
+
+ private static Geometry unwrap(Shape shape) {
+ assert (shape instanceof JtsGeometry): "shape is not a JTSGeometry";
+ return ((JtsGeometry)shape).getGeom();
+ }
+
+ public static void assertMultiPolygon(Shape shape) {
+ assert(unwrap(shape) instanceof MultiPolygon): "expected MultiPolygon but found " + unwrap(shape).getClass().getName();
+ }
+
+ public static void assertPolygon(Shape shape) {
+ assert(unwrap(shape) instanceof Polygon): "expected Polygon but found " + unwrap(shape).getClass().getName();
+ }
+
+ public static void assertLineString(Shape shape) {
+ assert(unwrap(shape) instanceof LineString): "expected LineString but found " + unwrap(shape).getClass().getName();
+ }
+
+ public static void assertMultiLineString(Shape shape) {
+ assert(unwrap(shape) instanceof MultiLineString): "expected MultiLineString but found " + unwrap(shape).getClass().getName();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/integration/search/geo/GeoFilterTests.java b/src/test/java/org/elasticsearch/test/integration/search/geo/GeoFilterTests.java
index 78b4122c71d..15b30418386 100644
--- a/src/test/java/org/elasticsearch/test/integration/search/geo/GeoFilterTests.java
+++ b/src/test/java/org/elasticsearch/test/integration/search/geo/GeoFilterTests.java
@@ -37,9 +37,9 @@ import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.GeoHashUtils;
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.geo.builders.ShapeBuilder;
+import org.elasticsearch.common.geo.builders.MultiPolygonBuilder;
+import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.FilterBuilders;
@@ -105,27 +105,27 @@ public class GeoFilterTests extends AbstractSharedClusterTest {
try {
// self intersection polygon
ShapeBuilder.newPolygon()
- .point(-10, -10)
- .point(10, 10)
- .point(-10, 10)
- .point(10, -10)
- .close().build();
+ .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(-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(-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();
@@ -136,8 +136,8 @@ public class GeoFilterTests extends AbstractSharedClusterTest {
try {
// polygon with intersection holes
ShapeBuilder.newPolygon()
- .point(-10, -10).point(-10, 10).point(10, 10).point(10, -10)
- .hole()
+ .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()
@@ -151,14 +151,14 @@ public class GeoFilterTests extends AbstractSharedClusterTest {
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();
+ .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) {
}
@@ -181,7 +181,7 @@ public class GeoFilterTests extends AbstractSharedClusterTest {
// Multipolygon: polygon with hole and polygon within the whole
ShapeBuilder.newMultiPolygon()
- .polygon()
+ .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)
@@ -240,9 +240,9 @@ public class GeoFilterTests extends AbstractSharedClusterTest {
// 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()
+ .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()
@@ -250,7 +250,8 @@ public class GeoFilterTests extends AbstractSharedClusterTest {
.point(-4, -4).point(-4, 4).point(4, 4).point(4, -4)
.close();
- BytesReference data = polygon.toXContent("area", jsonBuilder().startObject()).endObject().bytes();
+ BytesReference data = jsonBuilder().startObject().field("area", polygon).endObject().bytes();
+
client().prepareIndex("shapes", "polygon", "1").setSource(data).execute().actionGet();
client().admin().indices().prepareRefresh().execute().actionGet();
@@ -308,13 +309,13 @@ public class GeoFilterTests extends AbstractSharedClusterTest {
// 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(-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();
+ data = jsonBuilder().startObject().field("area", inverse).endObject().bytes();
client().prepareIndex("shapes", "polygon", "2").setSource(data).execute().actionGet();
client().admin().indices().prepareRefresh().execute().actionGet();
@@ -341,28 +342,53 @@ public class GeoFilterTests extends AbstractSharedClusterTest {
result = client().prepareSearch()
.setQuery(matchAllQuery())
- .setFilter(FilterBuilders.geoWithinFilter("area", builder.build()))
+ .setFilter(FilterBuilders.geoWithinFilter("area", builder))
.execute().actionGet();
assertHitCount(result, 2);
}
-/* 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(190, -10).point(190, 10).point(170, 10)
+ .close();
-// // 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();
+ data = jsonBuilder().startObject().field("area", builder).endObject().bytes();
+ client().prepareIndex("shapes", "polygon", "1").setSource(data).execute().actionGet();
+ client().admin().indices().prepareRefresh().execute().actionGet();
+
+ // Create a polygon crossing longitude 180 with hole.
+ builder = ShapeBuilder.newPolygon()
+ .point(170, -10).point(190, -10).point(190, 10).point(170, 10)
+ .hole().point(175, -5).point(185,-5).point(185,5).point(175,5).close()
+ .close();
+
+ data = jsonBuilder().startObject().field("area", builder).endObject().bytes();
+ client().prepareIndex("shapes", "polygon", "1").setSource(data).execute().actionGet();
+ client().admin().indices().prepareRefresh().execute().actionGet();
+
+ result = client().prepareSearch()
+ .setQuery(matchAllQuery())
+ .setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(174, -4)))
+ .execute().actionGet();
+ assertHitCount(result, 1);
+
+ result = client().prepareSearch()
+ .setQuery(matchAllQuery())
+ .setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(-174, -4)))
+ .execute().actionGet();
+ assertHitCount(result, 1);
+
+ result = client().prepareSearch()
+ .setQuery(matchAllQuery())
+ .setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(180, -4)))
+ .execute().actionGet();
+ assertHitCount(result, 0);
+
+ result = client().prepareSearch()
+ .setQuery(matchAllQuery())
+ .setFilter(FilterBuilders.geoIntersectionFilter("area", ShapeBuilder.newPoint(180, -6)))
+ .execute().actionGet();
+ assertHitCount(result, 1);
}
@Test
diff --git a/src/test/java/org/elasticsearch/test/integration/search/geo/GeoShapeIntegrationTests.java b/src/test/java/org/elasticsearch/test/integration/search/geo/GeoShapeIntegrationTests.java
index 24b72202a5f..8186bf945f4 100644
--- a/src/test/java/org/elasticsearch/test/integration/search/geo/GeoShapeIntegrationTests.java
+++ b/src/test/java/org/elasticsearch/test/integration/search/geo/GeoShapeIntegrationTests.java
@@ -19,7 +19,6 @@
package org.elasticsearch.test.integration.search.geo;
-import static org.elasticsearch.common.geo.ShapeBuilder.newRectangle;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.geoIntersectionFilter;
import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;
@@ -35,15 +34,13 @@ import java.util.List;
import java.util.Map;
import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.common.geo.GeoJSONShapeSerializer;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.test.integration.AbstractSharedClusterTest;
import org.testng.annotations.Test;
-import com.spatial4j.core.shape.Shape;
-
public class GeoShapeIntegrationTests extends AbstractSharedClusterTest {
@Test
@@ -74,7 +71,9 @@ public class GeoShapeIntegrationTests extends AbstractSharedClusterTest {
.endObject()).execute().actionGet();
refresh();
- Shape shape = newRectangle().topLeft(-45, 45).bottomRight(45, -45).build();
+ client().admin().indices().prepareRefresh().execute().actionGet();
+
+ ShapeBuilder shape = ShapeBuilder.newEnvelope().topLeft(-45, 45).bottomRight(45, -45);
SearchResponse searchResponse = client().prepareSearch()
.setQuery(filteredQuery(matchAllQuery(),
@@ -121,7 +120,7 @@ public class GeoShapeIntegrationTests extends AbstractSharedClusterTest {
client().admin().indices().prepareRefresh().execute().actionGet();
- Shape query = newRectangle().topLeft(-122.88, 48.62).bottomRight(-122.82, 48.54).build();
+ ShapeBuilder query = ShapeBuilder.newEnvelope().topLeft(-122.88, 48.62).bottomRight(-122.82, 48.54);
// This search would fail if both geoshape indexing and geoshape filtering
// used the bottom-level optimization in SpatialPrefixTree#recursiveGetNodes.
@@ -156,10 +155,9 @@ public class GeoShapeIntegrationTests extends AbstractSharedClusterTest {
refresh();
- Shape shape = newRectangle().topLeft(-45, 45).bottomRight(45, -45).build();
+ ShapeBuilder shape = ShapeBuilder.newEnvelope().topLeft(-45, 45).bottomRight(45, -45);
XContentBuilder shapeContent = jsonBuilder().startObject()
- .startObject("shape");
- GeoJSONShapeSerializer.serialize(shape, shapeContent);
+ .field("shape", shape);
shapeContent.endObject();
createIndex("shapes");
ensureGreen();
@@ -184,6 +182,26 @@ public class GeoShapeIntegrationTests extends AbstractSharedClusterTest {
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("1"));
}
+ @Test
+ public void testReusableBuilder() throws IOException {
+ ShapeBuilder polygon = ShapeBuilder.newPolygon()
+ .point(170, -10).point(190, -10).point(190, 10).point(170, 10)
+ .hole().point(175, -5).point(185,-5).point(185,5).point(175,5).close()
+ .close();
+ assertUnmodified(polygon);
+
+ ShapeBuilder linestring = ShapeBuilder.newLineString()
+ .point(170, -10).point(190, -10).point(190, 10).point(170, 10);
+ assertUnmodified(linestring);
+ }
+
+ private void assertUnmodified(ShapeBuilder builder) throws IOException {
+ String before = jsonBuilder().startObject().field("area", builder).endObject().string();
+ builder.build();
+ String after = jsonBuilder().startObject().field("area", builder).endObject().string();
+ assertThat(before, equalTo(after));
+ }
+
@Test
public void testParsingMultipleShapes() throws IOException {
String mapping = XContentFactory.jsonBuilder()
diff --git a/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeParserTests.java b/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeParserTests.java
index 99d1e05b197..c5f8f5ee029 100644
--- a/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeParserTests.java
+++ b/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeParserTests.java
@@ -1,21 +1,49 @@
+/*
+ * 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.unit.common.geo;
-import com.spatial4j.core.shape.Shape;
-import com.spatial4j.core.shape.jts.JtsGeometry;
-import com.spatial4j.core.shape.jts.JtsPoint;
-import com.vividsolutions.jts.geom.*;
-import org.elasticsearch.common.geo.GeoJSONShapeParser;
-import org.elasticsearch.common.geo.GeoShapeConstants;
-import org.elasticsearch.common.xcontent.XContentFactory;
-import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.common.xcontent.json.JsonXContent;
-import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import static org.testng.Assert.assertEquals;
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+import org.testng.annotations.Test;
+
+import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.jts.JtsGeometry;
+import com.spatial4j.core.shape.jts.JtsPoint;
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.LinearRing;
+import com.vividsolutions.jts.geom.MultiPoint;
+import com.vividsolutions.jts.geom.MultiPolygon;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+
+import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.assertEquals;
/**
* Tests for {@link GeoJSONShapeParser}
@@ -31,7 +59,7 @@ public class GeoJSONShapeParserTests {
.endObject().string();
Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
- assertGeometryEquals(new JtsPoint(expected, GeoShapeConstants.SPATIAL_CONTEXT), pointGeoJson);
+ assertGeometryEquals(new JtsPoint(expected, ShapeBuilder.SPATIAL_CONTEXT), pointGeoJson);
}
@Test
@@ -49,7 +77,7 @@ public class GeoJSONShapeParserTests {
LineString expected = GEOMETRY_FACTORY.createLineString(
lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
- assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), lineGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, ShapeBuilder.SPATIAL_CONTEXT, false), lineGeoJson);
}
@Test
@@ -57,11 +85,11 @@ public class GeoJSONShapeParserTests {
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
- .startArray().value(100.0).value(0.0).endArray()
- .startArray().value(101.0).value(0.0).endArray()
- .startArray().value(101.0).value(1.0).endArray()
.startArray().value(100.0).value(1.0).endArray()
+ .startArray().value(101.0).value(1.0).endArray()
+ .startArray().value(101.0).value(0.0).endArray()
.startArray().value(100.0).value(0.0).endArray()
+ .startArray().value(100.0).value(1.0).endArray()
.endArray()
.endArray()
.endObject().string();
@@ -73,10 +101,9 @@ public class GeoJSONShapeParserTests {
shellCoordinates.add(new Coordinate(100, 1));
shellCoordinates.add(new Coordinate(100, 0));
- 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);
- assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), polygonGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, ShapeBuilder.SPATIAL_CONTEXT, false), polygonGeoJson);
}
@Test
@@ -84,18 +111,18 @@ public class GeoJSONShapeParserTests {
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
- .startArray().value(100.0).value(0.0).endArray()
- .startArray().value(101.0).value(0.0).endArray()
- .startArray().value(101.0).value(1.0).endArray()
.startArray().value(100.0).value(1.0).endArray()
+ .startArray().value(101.0).value(1.0).endArray()
+ .startArray().value(101.0).value(0.0).endArray()
.startArray().value(100.0).value(0.0).endArray()
+ .startArray().value(100.0).value(1.0).endArray()
.endArray()
.startArray()
+ .startArray().value(100.2).value(0.8).endArray()
.startArray().value(100.2).value(0.2).endArray()
.startArray().value(100.8).value(0.2).endArray()
.startArray().value(100.8).value(0.8).endArray()
.startArray().value(100.2).value(0.8).endArray()
- .startArray().value(100.2).value(0.2).endArray()
.endArray()
.endArray()
.endObject().string();
@@ -120,7 +147,7 @@ public class GeoJSONShapeParserTests {
holes[0] = GEOMETRY_FACTORY.createLinearRing(
holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes);
- assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), polygonGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, ShapeBuilder.SPATIAL_CONTEXT, false), polygonGeoJson);
}
@Test
@@ -138,7 +165,7 @@ public class GeoJSONShapeParserTests {
MultiPoint expected = GEOMETRY_FACTORY.createMultiPoint(
multiPointCoordinates.toArray(new Coordinate[multiPointCoordinates.size()]));
- assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), multiPointGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, ShapeBuilder.SPATIAL_CONTEXT, false), multiPointGeoJson);
}
@Test
@@ -163,11 +190,11 @@ public class GeoJSONShapeParserTests {
.startArray().value(100.0).value(0.0).endArray()
.endArray()
.startArray()
+ .startArray().value(100.2).value(0.8).endArray()
.startArray().value(100.2).value(0.2).endArray()
.startArray().value(100.8).value(0.2).endArray()
.startArray().value(100.8).value(0.8).endArray()
.startArray().value(100.2).value(0.8).endArray()
- .startArray().value(100.2).value(0.2).endArray()
.endArray()
.endArray()
.endArray()
@@ -187,27 +214,25 @@ public class GeoJSONShapeParserTests {
holeCoordinates.add(new Coordinate(100.2, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.2));
- LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
- shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
+ LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
LinearRing[] holes = new LinearRing[1];
- holes[0] = GEOMETRY_FACTORY.createLinearRing(
- holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
+ holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes);
shellCoordinates = new ArrayList();
- shellCoordinates.add(new Coordinate(102, 2));
- shellCoordinates.add(new Coordinate(103, 2));
- shellCoordinates.add(new Coordinate(103, 3));
shellCoordinates.add(new Coordinate(102, 3));
+ shellCoordinates.add(new Coordinate(103, 3));
+ shellCoordinates.add(new Coordinate(103, 2));
shellCoordinates.add(new Coordinate(102, 2));
+ shellCoordinates.add(new Coordinate(102, 3));
- 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);
MultiPolygon expected = GEOMETRY_FACTORY.createMultiPolygon(new Polygon[] {withoutHoles, withHoles});
- assertGeometryEquals(new JtsGeometry(expected, GeoShapeConstants.SPATIAL_CONTEXT, false), multiPolygonGeoJson);
+ assertGeometryEquals(new JtsGeometry(expected, ShapeBuilder.SPATIAL_CONTEXT, false), multiPolygonGeoJson);
}
@Test
@@ -228,12 +253,13 @@ public class GeoJSONShapeParserTests {
.endObject().string();
Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
- assertGeometryEquals(new JtsPoint(expected, GeoShapeConstants.SPATIAL_CONTEXT), pointGeoJson);
+ assertGeometryEquals(new JtsPoint(expected, ShapeBuilder.SPATIAL_CONTEXT), pointGeoJson);
}
private void assertGeometryEquals(Shape expected, String geoJson) throws IOException {
XContentParser parser = JsonXContent.jsonXContent.createParser(geoJson);
parser.nextToken();
- assertEquals(GeoJSONShapeParser.parse(parser), expected);
+ assertEquals(ShapeBuilder.parse(parser).build(), expected);
}
+
}
diff --git a/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeSerializerTests.java b/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeSerializerTests.java
deleted file mode 100644
index 06f32a55a49..00000000000
--- a/src/test/java/org/elasticsearch/test/unit/common/geo/GeoJSONShapeSerializerTests.java
+++ /dev/null
@@ -1,224 +0,0 @@
-package org.elasticsearch.test.unit.common.geo;
-
-import com.spatial4j.core.shape.Shape;
-import com.spatial4j.core.shape.jts.JtsGeometry;
-import com.spatial4j.core.shape.jts.JtsPoint;
-import com.vividsolutions.jts.geom.*;
-import org.elasticsearch.common.geo.GeoJSONShapeSerializer;
-import org.elasticsearch.common.geo.GeoShapeConstants;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.testng.Assert.assertEquals;
-
-/**
- * Tests for {@link GeoJSONShapeSerializer}
- */
-public class GeoJSONShapeSerializerTests {
-
- private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
-
- @Test
- public void testSerialize_simplePoint() throws IOException {
- XContentBuilder expected = XContentFactory.jsonBuilder().startObject().field("type", "Point")
- .startArray("coordinates").value(100.0).value(0.0).endArray()
- .endObject();
-
- Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
- assertSerializationEquals(expected, new JtsPoint(point, GeoShapeConstants.SPATIAL_CONTEXT));
- }
-
- @Test
- public void testSerialize_lineString() throws IOException {
- XContentBuilder expected = XContentFactory.jsonBuilder().startObject().field("type", "LineString")
- .startArray("coordinates")
- .startArray().value(100.0).value(0.0).endArray()
- .startArray().value(101.0).value(1.0).endArray()
- .endArray()
- .endObject();
-
- List lineCoordinates = new ArrayList();
- lineCoordinates.add(new Coordinate(100, 0));
- lineCoordinates.add(new Coordinate(101, 1));
-
- LineString lineString = GEOMETRY_FACTORY.createLineString(
- lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
-
- assertSerializationEquals(expected, new JtsGeometry(lineString, GeoShapeConstants.SPATIAL_CONTEXT, false));
- }
-
- @Test
- public void testSerialize_polygonNoHoles() throws IOException {
- XContentBuilder expected = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
- .startArray("coordinates")
- .startArray()
- .startArray().value(100.0).value(0.0).endArray()
- .startArray().value(101.0).value(0.0).endArray()
- .startArray().value(101.0).value(1.0).endArray()
- .startArray().value(100.0).value(1.0).endArray()
- .startArray().value(100.0).value(0.0).endArray()
- .endArray()
- .endArray()
- .endObject();
-
- List shellCoordinates = new ArrayList();
- shellCoordinates.add(new Coordinate(100, 0));
- shellCoordinates.add(new Coordinate(101, 0));
- shellCoordinates.add(new Coordinate(101, 1));
- shellCoordinates.add(new Coordinate(100, 1));
- shellCoordinates.add(new Coordinate(100, 0));
-
- LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
- shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
- Polygon polygon = GEOMETRY_FACTORY.createPolygon(shell, null);
-
- assertSerializationEquals(expected, new JtsGeometry(polygon, GeoShapeConstants.SPATIAL_CONTEXT, false));
- }
-
- @Test
- public void testSerialize_polygonWithHole() throws IOException {
- XContentBuilder expected = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
- .startArray("coordinates")
- .startArray()
- .startArray().value(100.0).value(0.0).endArray()
- .startArray().value(101.0).value(0.0).endArray()
- .startArray().value(101.0).value(1.0).endArray()
- .startArray().value(100.0).value(1.0).endArray()
- .startArray().value(100.0).value(0.0).endArray()
- .endArray()
- .startArray()
- .startArray().value(100.2).value(0.2).endArray()
- .startArray().value(100.8).value(0.2).endArray()
- .startArray().value(100.8).value(0.8).endArray()
- .startArray().value(100.2).value(0.8).endArray()
- .startArray().value(100.2).value(0.2).endArray()
- .endArray()
- .endArray()
- .endObject();
-
- List shellCoordinates = new ArrayList();
- shellCoordinates.add(new Coordinate(100, 0));
- shellCoordinates.add(new Coordinate(101, 0));
- shellCoordinates.add(new Coordinate(101, 1));
- shellCoordinates.add(new Coordinate(100, 1));
- shellCoordinates.add(new Coordinate(100, 0));
-
- List holeCoordinates = new ArrayList();
- holeCoordinates.add(new Coordinate(100.2, 0.2));
- holeCoordinates.add(new Coordinate(100.8, 0.2));
- holeCoordinates.add(new Coordinate(100.8, 0.8));
- holeCoordinates.add(new Coordinate(100.2, 0.8));
- holeCoordinates.add(new Coordinate(100.2, 0.2));
-
- LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
- shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
- LinearRing[] holes = new LinearRing[1];
- holes[0] = GEOMETRY_FACTORY.createLinearRing(
- holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
- Polygon polygon = GEOMETRY_FACTORY.createPolygon(shell, holes);
-
- assertSerializationEquals(expected, new JtsGeometry(polygon, GeoShapeConstants.SPATIAL_CONTEXT, false));
- }
-
- @Test
- public void testSerialize_multiPoint() throws IOException {
- XContentBuilder expected = XContentFactory.jsonBuilder().startObject().field("type", "MultiPoint")
- .startArray("coordinates")
- .startArray().value(100.0).value(0.0).endArray()
- .startArray().value(101.0).value(1.0).endArray()
- .endArray()
- .endObject();
-
- List multiPointCoordinates = new ArrayList();
- multiPointCoordinates.add(new Coordinate(100, 0));
- multiPointCoordinates.add(new Coordinate(101, 1));
-
- MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPoint(
- multiPointCoordinates.toArray(new Coordinate[multiPointCoordinates.size()]));
-
- assertSerializationEquals(expected, new JtsGeometry(multiPoint, GeoShapeConstants.SPATIAL_CONTEXT, false));
- }
-
- @Test
- public void testSerialize_multiPolygon() throws IOException {
- XContentBuilder expected = XContentFactory.jsonBuilder().startObject().field("type", "MultiPolygon")
- .startArray("coordinates")
- .startArray()
- .startArray()
- .startArray().value(100.0).value(0.0).endArray()
- .startArray().value(101.0).value(0.0).endArray()
- .startArray().value(101.0).value(1.0).endArray()
- .startArray().value(100.0).value(1.0).endArray()
- .startArray().value(100.0).value(0.0).endArray()
- .endArray()
- .startArray()
- .startArray().value(100.2).value(0.2).endArray()
- .startArray().value(100.8).value(0.2).endArray()
- .startArray().value(100.8).value(0.8).endArray()
- .startArray().value(100.2).value(0.8).endArray()
- .startArray().value(100.2).value(0.2).endArray()
- .endArray()
- .endArray()
- .startArray()
- .startArray()
- .startArray().value(102.0).value(2.0).endArray()
- .startArray().value(103.0).value(2.0).endArray()
- .startArray().value(103.0).value(3.0).endArray()
- .startArray().value(102.0).value(3.0).endArray()
- .startArray().value(102.0).value(2.0).endArray()
- .endArray()
- .endArray()
-
- .endArray()
- .endObject();
-
- List shellCoordinates = new ArrayList();
- shellCoordinates.add(new Coordinate(100, 0));
- shellCoordinates.add(new Coordinate(101, 0));
- shellCoordinates.add(new Coordinate(101, 1));
- shellCoordinates.add(new Coordinate(100, 1));
- shellCoordinates.add(new Coordinate(100, 0));
-
- List holeCoordinates = new ArrayList();
- holeCoordinates.add(new Coordinate(100.2, 0.2));
- holeCoordinates.add(new Coordinate(100.8, 0.2));
- holeCoordinates.add(new Coordinate(100.8, 0.8));
- holeCoordinates.add(new Coordinate(100.2, 0.8));
- holeCoordinates.add(new Coordinate(100.2, 0.2));
-
- LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
- shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
- LinearRing[] holes = new LinearRing[1];
- holes[0] = GEOMETRY_FACTORY.createLinearRing(
- holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
- Polygon withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes);
-
- shellCoordinates = new ArrayList();
- shellCoordinates.add(new Coordinate(102, 2));
- shellCoordinates.add(new Coordinate(103, 2));
- shellCoordinates.add(new Coordinate(103, 3));
- shellCoordinates.add(new Coordinate(102, 3));
- shellCoordinates.add(new Coordinate(102, 2));
-
- shell = GEOMETRY_FACTORY.createLinearRing(
- shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
- Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null);
-
- MultiPolygon multiPolygon = GEOMETRY_FACTORY.createMultiPolygon(new Polygon[] {withHoles, withoutHoles});
-
- assertSerializationEquals(expected, new JtsGeometry(multiPolygon, GeoShapeConstants.SPATIAL_CONTEXT, false));
- }
-
- private void assertSerializationEquals(XContentBuilder expected, Shape shape) throws IOException {
- XContentBuilder builder = XContentFactory.jsonBuilder();
- builder.startObject();
- GeoJSONShapeSerializer.serialize(shape, builder);
- builder.endObject();
- assertEquals(expected.string(), builder.string());
- }
-}
diff --git a/src/test/java/org/elasticsearch/test/unit/common/geo/ShapeBuilderTests.java b/src/test/java/org/elasticsearch/test/unit/common/geo/ShapeBuilderTests.java
index e397bfa7288..5f41567915f 100644
--- a/src/test/java/org/elasticsearch/test/unit/common/geo/ShapeBuilderTests.java
+++ b/src/test/java/org/elasticsearch/test/unit/common/geo/ShapeBuilderTests.java
@@ -1,17 +1,37 @@
+/*
+ * 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.unit.common.geo;
+import static org.testng.Assert.assertEquals;
+
+import org.elasticsearch.common.geo.builders.ShapeBuilder;
+import org.testng.annotations.Test;
+
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Polygon;
-import org.elasticsearch.common.geo.ShapeBuilder;
-import org.testng.annotations.Test;
-
-import static org.testng.Assert.assertEquals;
+import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.*;
/**
* Tests for {@link ShapeBuilder}
*/
@@ -19,14 +39,14 @@ public class ShapeBuilderTests {
@Test
public void testNewPoint() {
- Point point = ShapeBuilder.newPoint(-100, 45);
+ Point point = ShapeBuilder.newPoint(-100, 45).build();
assertEquals(-100D, point.getX());
assertEquals(45D, point.getY());
}
@Test
public void testNewRectangle() {
- Rectangle rectangle = ShapeBuilder.newRectangle().topLeft(-45, 30).bottomRight(45, -30).build();
+ Rectangle rectangle = ShapeBuilder.newEnvelope().topLeft(-45, 30).bottomRight(45, -30).build();
assertEquals(-45D, rectangle.getMinX());
assertEquals(-30D, rectangle.getMinY());
assertEquals(45D, rectangle.getMaxX());
@@ -48,26 +68,130 @@ public class ShapeBuilderTests {
assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30));
assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30));
}
+
+ @Test
+ public void testLineStringBuilder() {
+ // Building a simple LineString
+ ShapeBuilder.newLineString()
+ .point(-130.0, 55.0)
+ .point(-130.0, -40.0)
+ .point(-15.0, -40.0)
+ .point(-20.0, 50.0)
+ .point(-45.0, 50.0)
+ .point(-45.0, -15.0)
+ .point(-110.0, -15.0)
+ .point(-110.0, 55.0).build();
+
+ // Building a linestring that needs to be wrapped
+ ShapeBuilder.newLineString()
+ .point(100.0, 50.0)
+ .point(110.0, -40.0)
+ .point(240.0, -40.0)
+ .point(230.0, 60.0)
+ .point(200.0, 60.0)
+ .point(200.0, -30.0)
+ .point(130.0, -30.0)
+ .point(130.0, 60.0)
+ .build();
+
+ // Building a lineString on the dateline
+ ShapeBuilder.newLineString()
+ .point(-180.0, 80.0)
+ .point(-180.0, 40.0)
+ .point(-180.0, -40.0)
+ .point(-180.0, -80.0)
+ .build();
+
+ // Building a lineString on the dateline
+ ShapeBuilder.newLineString()
+ .point(180.0, 80.0)
+ .point(180.0, 40.0)
+ .point(180.0, -40.0)
+ .point(180.0, -80.0)
+ .build();
+ }
@Test
- public void testToJTSGeometry() {
- ShapeBuilder.PolygonBuilder polygonBuilder = ShapeBuilder.newPolygon()
- .point(-45, 30)
- .point(45, 30)
- .point(45, -30)
- .point(-45, -30)
- .close();
+ public void testMultiLineString() {
+ ShapeBuilder.newMultiLinestring()
+ .linestring()
+ .point(-100.0, 50.0)
+ .point(50.0, 50.0)
+ .point(50.0, 20.0)
+ .point(-100.0, 20.0)
+ .end()
+ .linestring()
+ .point(-100.0, 20.0)
+ .point(50.0, 20.0)
+ .point(50.0, 0.0)
+ .point(-100.0, 0.0)
+ .end()
+ .build();
- Shape polygon = polygonBuilder.build();
- Geometry polygonGeometry = ShapeBuilder.toJTSGeometry(polygon);
- assertEquals(polygonBuilder.toPolygon(), polygonGeometry);
-
- Rectangle rectangle = ShapeBuilder.newRectangle().topLeft(-45, 30).bottomRight(45, -30).build();
- Geometry rectangleGeometry = ShapeBuilder.toJTSGeometry(rectangle);
- assertEquals(rectangleGeometry, polygonGeometry);
-
- Point point = ShapeBuilder.newPoint(-45, 30);
- Geometry pointGeometry = ShapeBuilder.toJTSGeometry(point);
- assertEquals(pointGeometry.getCoordinate(), new Coordinate(-45, 30));
+
+ // LineString that needs to be wrappped
+ ShapeBuilder.newMultiLinestring()
+ .linestring()
+ .point(150.0, 60.0)
+ .point(200.0, 60.0)
+ .point(200.0, 40.0)
+ .point(150.0, 40.0)
+ .end()
+ .linestring()
+ .point(150.0, 20.0)
+ .point(200.0, 20.0)
+ .point(200.0, 0.0)
+ .point(150.0, 0.0)
+ .end()
+ .build();
}
+
+ @Test
+ public void testPolygonSelfIntersection() {
+ try {
+ ShapeBuilder.newPolygon()
+ .point(-40.0, 50.0)
+ .point(40.0, 50.0)
+ .point(-40.0, -50.0)
+ .point(40.0, -50.0)
+ .close().build();
+ assert false : "Polygon self-intersection";
+ } catch (Throwable e) {}
+
+ }
+
+ @Test
+ public void testGeoCircle() {
+ ShapeBuilder.newCircleBuilder().center(0, 0).radius("100m").build();
+ ShapeBuilder.newCircleBuilder().center(+180, 0).radius("100m").build();
+ ShapeBuilder.newCircleBuilder().center(-180, 0).radius("100m").build();
+ ShapeBuilder.newCircleBuilder().center(0, 90).radius("100m").build();
+ ShapeBuilder.newCircleBuilder().center(0, -90).radius("100m").build();
+ }
+
+ @Test
+ public void testPolygonWrapping() {
+ Shape shape = ShapeBuilder.newPolygon()
+ .point(-150.0, 65.0)
+ .point(-250.0, 65.0)
+ .point(-250.0, -65.0)
+ .point(-150.0, -65.0)
+ .close().build();
+
+ assertMultiPolygon(shape);
+ }
+
+ @Test
+ public void testLineStringWrapping() {
+ Shape shape = ShapeBuilder.newLineString()
+ .point(-150.0, 65.0)
+ .point(-250.0, 65.0)
+ .point(-250.0, -65.0)
+ .point(-150.0, -65.0)
+ .build();
+
+ assertMultiLineString(shape);
+ }
+
+
}