mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-03-09 14:34:43 +00:00
Geo-Refactoring
=============== The code handling geo-shapes is not centralized and creating points takes place at different places. Also the collection of supported geo_shapes is not complete regarding to the GEOJSon specification. This commit centralizes the code related to GEO calculations and extends the old API by a set of new shapes. Null-Shapes =========== The latest implementation of geo-shapes allows to index null-shapes. This means a field that is defined to hold a geo-shape can be set to null. In example: { "shape": null } New Shapes ========== The geo-shapes multipoint and multilinestring have been added to the geo_shape types. Also geo_circle is introduced by this commit. Dateline wrapping ================= A major issue of geo-shapes is the spherical geometry. Since ElasticSearch works on the Geo-Coordinates by wrapping the Earths surface to a plane, some shapes are hard to define if it’s crossing the +180°, -180 longitude. To solve this issue ElasticSearch offers the possibility to define geo shapes crossing this borders and decompose these shapes and automatically re-compose them in a spherical manner. This feature may change the indexed shape-type. If for example a polygon is defined, that crosses the dateline, it will be re-assembled to a set of polygons. This causes indexing a multipolygon. Also linestrings crossing the dateline might be re-assembled to multilinestrings. Builders ======== The API has been refactored to use builders instead of using shapes. So parsing geo-shapes will result in builder objects. These builders can be parsed and serialized without generating any shapes. this causes shape generation only on the nodes executing the actual operation. Also the baseclass ShapeBuilder implements the ToXContent interface which allows to set fields of XContent directly. TODO’s ====== - The geo-circle will not work, if it’s crossing the dateline - The envelope also needs to wrapped Closes #1997 #2708
This commit is contained in:
parent
0643569e70
commit
0c2d12bda3
@ -1,230 +0,0 @@
|
||||
/*
|
||||
* Licensed to ElasticSearch and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. ElasticSearch licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import com.spatial4j.core.shape.Shape;
|
||||
import com.spatial4j.core.shape.impl.RectangleImpl;
|
||||
import com.spatial4j.core.shape.jts.JtsGeometry;
|
||||
import com.spatial4j.core.shape.jts.JtsPoint;
|
||||
import com.vividsolutions.jts.geom.Coordinate;
|
||||
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||
import com.vividsolutions.jts.geom.LinearRing;
|
||||
import com.vividsolutions.jts.geom.Polygon;
|
||||
import org.elasticsearch.ElasticSearchParseException;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Parsers which supports reading {@link Shape}s in GeoJSON format from a given
|
||||
* {@link XContentParser}.
|
||||
* <p/>
|
||||
* An example of the format used for polygons:
|
||||
* <p/>
|
||||
* {
|
||||
* "type": "Polygon",
|
||||
* "coordinates": [
|
||||
* [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
|
||||
* [100.0, 1.0], [100.0, 0.0] ]
|
||||
* ]
|
||||
* }
|
||||
* <p/>
|
||||
* Note, currently MultiPolygon and GeometryCollections are not supported
|
||||
*/
|
||||
public class GeoJSONShapeParser {
|
||||
|
||||
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
|
||||
|
||||
private GeoJSONShapeParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the current object from the given {@link XContentParser}, creating
|
||||
* the {@link Shape} representation
|
||||
*
|
||||
* @param parser Parser that will be read from
|
||||
* @return Shape representation of the geojson defined Shape
|
||||
* @throws IOException Thrown if an error occurs while reading from the XContentParser
|
||||
*/
|
||||
public static Shape parse(XContentParser parser) throws IOException {
|
||||
if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
|
||||
throw new ElasticSearchParseException("Shape must be an object consisting of type and coordinates");
|
||||
}
|
||||
|
||||
String shapeType = 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 ("type".equals(fieldName)) {
|
||||
parser.nextToken();
|
||||
shapeType = parser.text().toLowerCase(Locale.ENGLISH);
|
||||
if (shapeType == null) {
|
||||
throw new ElasticSearchParseException("Unknown Shape type [" + parser.text() + "]");
|
||||
}
|
||||
} else if ("coordinates".equals(fieldName)) {
|
||||
parser.nextToken();
|
||||
node = parseCoordinates(parser);
|
||||
} 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");
|
||||
}
|
||||
|
||||
return buildShape(shapeType, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<CoordinateNode> nodes = new ArrayList<CoordinateNode>();
|
||||
while (token != XContentParser.Token.END_ARRAY) {
|
||||
nodes.add(parseCoordinates(parser));
|
||||
token = parser.nextToken();
|
||||
}
|
||||
|
||||
return new CoordinateNode(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the actual {@link Shape} with the given shape type from the tree
|
||||
* of coordinates
|
||||
*
|
||||
* @param shapeType Type of Shape to be built
|
||||
* @param node Root node of the coordinate tree
|
||||
* @return Shape built from the coordinates
|
||||
*/
|
||||
private static Shape buildShape(String shapeType, CoordinateNode node) {
|
||||
if ("point".equals(shapeType)) {
|
||||
return new JtsPoint(GEOMETRY_FACTORY.createPoint(node.coordinate), GeoShapeConstants.SPATIAL_CONTEXT);
|
||||
} else if ("linestring".equals(shapeType)) {
|
||||
return new JtsGeometry(GEOMETRY_FACTORY.createLineString(toCoordinates(node)), GeoShapeConstants.SPATIAL_CONTEXT, true);
|
||||
} else if ("polygon".equals(shapeType)) {
|
||||
return new JtsGeometry(buildPolygon(node), GeoShapeConstants.SPATIAL_CONTEXT, true);
|
||||
} else if ("multipoint".equals(shapeType)) {
|
||||
return new JtsGeometry(GEOMETRY_FACTORY.createMultiPoint(toCoordinates(node)), GeoShapeConstants.SPATIAL_CONTEXT, true);
|
||||
} else if ("envelope".equals(shapeType)) {
|
||||
Coordinate[] coordinates = toCoordinates(node);
|
||||
return new RectangleImpl(coordinates[0].x, coordinates[1].x, coordinates[1].y, coordinates[0].y, GeoShapeConstants.SPATIAL_CONTEXT);
|
||||
} else if ("multipolygon".equals(shapeType)) {
|
||||
Polygon[] polygons = new Polygon[node.children.size()];
|
||||
for (int i = 0; i < node.children.size(); i++) {
|
||||
polygons[i] = buildPolygon(node.children.get(i));
|
||||
}
|
||||
return new JtsGeometry(
|
||||
GEOMETRY_FACTORY.createMultiPolygon(polygons),
|
||||
GeoShapeConstants.SPATIAL_CONTEXT,
|
||||
true);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("ShapeType [" + shapeType + "] not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link Polygon} from the given CoordinateNode
|
||||
*
|
||||
* @param node CoordinateNode that the Polygon will be built from
|
||||
* @return Polygon consisting of the coordinates in the CoordinateNode
|
||||
*/
|
||||
private static Polygon buildPolygon(CoordinateNode node) {
|
||||
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(toCoordinates(node.children.get(0)));
|
||||
LinearRing[] holes = null;
|
||||
if (node.children.size() > 1) {
|
||||
holes = new LinearRing[node.children.size() - 1];
|
||||
for (int i = 0; i < node.children.size() - 1; i++) {
|
||||
holes[i] = GEOMETRY_FACTORY.createLinearRing(toCoordinates(node.children.get(i + 1)));
|
||||
}
|
||||
}
|
||||
return GEOMETRY_FACTORY.createPolygon(shell, holes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the children of the given CoordinateNode into an array of
|
||||
* {@link Coordinate}.
|
||||
*
|
||||
* @param node CoordinateNode whose children will be converted
|
||||
* @return Coordinate array with the values taken from the children of the Node
|
||||
*/
|
||||
private static Coordinate[] toCoordinates(CoordinateNode node) {
|
||||
Coordinate[] coordinates = new Coordinate[node.children.size()];
|
||||
for (int i = 0; i < node.children.size(); i++) {
|
||||
coordinates[i] = node.children.get(i).coordinate;
|
||||
}
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node used to represent a tree of coordinates.
|
||||
* <p/>
|
||||
* Can either be a leaf node consisting of a Coordinate, or a parent with children
|
||||
*/
|
||||
private static class CoordinateNode {
|
||||
|
||||
private Coordinate coordinate;
|
||||
private List<CoordinateNode> children;
|
||||
|
||||
/**
|
||||
* Creates a new leaf CoordinateNode
|
||||
*
|
||||
* @param coordinate Coordinate for the Node
|
||||
*/
|
||||
private CoordinateNode(Coordinate coordinate) {
|
||||
this.coordinate = coordinate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new parent CoordinateNode
|
||||
*
|
||||
* @param children Children of the Node
|
||||
*/
|
||||
private CoordinateNode(List<CoordinateNode> children) {
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,232 +0,0 @@
|
||||
/*
|
||||
* Licensed to ElasticSearch and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. ElasticSearch licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import com.spatial4j.core.shape.Rectangle;
|
||||
import com.spatial4j.core.shape.Shape;
|
||||
import com.spatial4j.core.shape.jts.JtsGeometry;
|
||||
import com.vividsolutions.jts.geom.*;
|
||||
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Serializes {@link Shape} instances into GeoJSON format
|
||||
* <p/>
|
||||
* Example of the format used for points:
|
||||
* <p/>
|
||||
* { "type": "Point", "coordinates": [100.0, 0.0] }
|
||||
*/
|
||||
public class GeoJSONShapeSerializer {
|
||||
|
||||
private GeoJSONShapeSerializer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link Shape} as GeoJSON format into the given
|
||||
* {@link XContentBuilder}
|
||||
*
|
||||
* @param shape Shape that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
public static void serialize(Shape shape, XContentBuilder builder) throws IOException {
|
||||
if (shape instanceof JtsGeometry) {
|
||||
Geometry geometry = ((JtsGeometry) shape).getGeom();
|
||||
if (geometry instanceof Point) {
|
||||
serializePoint((Point) geometry, builder);
|
||||
} else if (geometry instanceof LineString) {
|
||||
serializeLineString((LineString) geometry, builder);
|
||||
} else if (geometry instanceof Polygon) {
|
||||
serializePolygon((Polygon) geometry, builder);
|
||||
} else if (geometry instanceof MultiPoint) {
|
||||
serializeMultiPoint((MultiPoint) geometry, builder);
|
||||
} else if (geometry instanceof MultiPolygon) {
|
||||
serializeMulitPolygon((MultiPolygon) geometry, builder);
|
||||
} else {
|
||||
throw new ElasticSearchIllegalArgumentException("Geometry type [" + geometry.getGeometryType() + "] not supported");
|
||||
}
|
||||
} else if (shape instanceof com.spatial4j.core.shape.Point) {
|
||||
serializePoint((com.spatial4j.core.shape.Point) shape, builder);
|
||||
} else if (shape instanceof Rectangle) {
|
||||
serializeRectangle((Rectangle) shape, builder);
|
||||
} else {
|
||||
throw new ElasticSearchIllegalArgumentException("Shape type [" + shape.getClass().getSimpleName() + "] not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link Rectangle}
|
||||
*
|
||||
* @param rectangle Rectangle that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializeRectangle(Rectangle rectangle, XContentBuilder builder) throws IOException {
|
||||
builder.field("type", "Envelope")
|
||||
.startArray("coordinates")
|
||||
.startArray().value(rectangle.getMinX()).value(rectangle.getMaxY()).endArray()
|
||||
.startArray().value(rectangle.getMaxX()).value(rectangle.getMinY()).endArray()
|
||||
.endArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link Point}
|
||||
*
|
||||
* @param point Point that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializePoint(Point point, XContentBuilder builder) throws IOException {
|
||||
builder.field("type", "Point")
|
||||
.startArray("coordinates")
|
||||
.value(point.getX()).value(point.getY())
|
||||
.endArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link com.spatial4j.core.shape.Point}
|
||||
*
|
||||
* @param point Point that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializePoint(com.spatial4j.core.shape.Point point, XContentBuilder builder) throws IOException {
|
||||
builder.field("type", "Point")
|
||||
.startArray("coordinates")
|
||||
.value(point.getX()).value(point.getY())
|
||||
.endArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link LineString}
|
||||
*
|
||||
* @param lineString LineString that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializeLineString(LineString lineString, XContentBuilder builder) throws IOException {
|
||||
builder.field("type", "LineString")
|
||||
.startArray("coordinates");
|
||||
|
||||
for (Coordinate coordinate : lineString.getCoordinates()) {
|
||||
serializeCoordinate(coordinate, builder);
|
||||
}
|
||||
|
||||
builder.endArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link Polygon}
|
||||
*
|
||||
* @param polygon Polygon that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializePolygon(Polygon polygon, XContentBuilder builder) throws IOException {
|
||||
builder.field("type", "Polygon")
|
||||
.startArray("coordinates");
|
||||
|
||||
serializePolygonCoordinates(polygon, builder);
|
||||
|
||||
builder.endArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the actual coordinates of the given {@link Polygon}
|
||||
*
|
||||
* @param polygon Polygon whose coordinates will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializePolygonCoordinates(Polygon polygon, XContentBuilder builder) throws IOException {
|
||||
builder.startArray(); // start outer ring
|
||||
|
||||
for (Coordinate coordinate : polygon.getExteriorRing().getCoordinates()) {
|
||||
serializeCoordinate(coordinate, builder);
|
||||
}
|
||||
|
||||
builder.endArray(); // end outer ring
|
||||
|
||||
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
|
||||
LineString interiorRing = polygon.getInteriorRingN(i);
|
||||
|
||||
builder.startArray();
|
||||
|
||||
for (Coordinate coordinate : interiorRing.getCoordinates()) {
|
||||
serializeCoordinate(coordinate, builder);
|
||||
}
|
||||
|
||||
builder.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link MultiPolygon}
|
||||
*
|
||||
* @param multiPolygon MultiPolygon that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializeMulitPolygon(MultiPolygon multiPolygon, XContentBuilder builder) throws IOException {
|
||||
builder.field("type", "MultiPolygon")
|
||||
.startArray("coordinates");
|
||||
|
||||
for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
|
||||
builder.startArray();
|
||||
|
||||
serializePolygonCoordinates((Polygon) multiPolygon.getGeometryN(i), builder);
|
||||
|
||||
builder.endArray();
|
||||
}
|
||||
|
||||
builder.endArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link MultiPoint}
|
||||
*
|
||||
* @param multiPoint MultiPoint that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializeMultiPoint(MultiPoint multiPoint, XContentBuilder builder) throws IOException {
|
||||
builder.field("type", "MultiPoint")
|
||||
.startArray("coordinates");
|
||||
|
||||
for (Coordinate coordinate : multiPoint.getCoordinates()) {
|
||||
serializeCoordinate(coordinate, builder);
|
||||
}
|
||||
|
||||
builder.endArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given {@link Coordinate}
|
||||
*
|
||||
* @param coordinate Coordinate that will be serialized
|
||||
* @param builder XContentBuilder it will be serialized to
|
||||
* @throws IOException Thrown if an error occurs while writing to the XContentBuilder
|
||||
*/
|
||||
private static void serializeCoordinate(Coordinate coordinate, XContentBuilder builder) throws IOException {
|
||||
builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
|
||||
}
|
||||
}
|
@ -1,424 +0,0 @@
|
||||
/*
|
||||
* Licensed to ElasticSearch and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. ElasticSearch licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.spatial4j.core.shape.Point;
|
||||
import com.spatial4j.core.shape.Rectangle;
|
||||
import com.spatial4j.core.shape.Shape;
|
||||
import com.spatial4j.core.shape.impl.PointImpl;
|
||||
import com.spatial4j.core.shape.impl.RectangleImpl;
|
||||
import com.spatial4j.core.shape.jts.JtsGeometry;
|
||||
import com.spatial4j.core.shape.jts.JtsPoint;
|
||||
import com.vividsolutions.jts.geom.Coordinate;
|
||||
import com.vividsolutions.jts.geom.Geometry;
|
||||
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||
import com.vividsolutions.jts.geom.LinearRing;
|
||||
import com.vividsolutions.jts.geom.MultiPolygon;
|
||||
import com.vividsolutions.jts.geom.Polygon;
|
||||
|
||||
/**
|
||||
* Utility class for building {@link Shape} instances like {@link Point},
|
||||
* {@link Rectangle} and Polygons.
|
||||
*/
|
||||
public class ShapeBuilder {
|
||||
|
||||
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
|
||||
|
||||
private ShapeBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Point}
|
||||
*
|
||||
* @param lon Longitude of point
|
||||
* @param lat Latitude of point
|
||||
* @return Point with the latitude and longitude
|
||||
*/
|
||||
public static Point newPoint(double lon, double lat) {
|
||||
return new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link RectangleBuilder} to build a {@link Rectangle}
|
||||
*
|
||||
* @return RectangleBuilder instance
|
||||
*/
|
||||
public static RectangleBuilder newRectangle() {
|
||||
return new RectangleBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PolygonBuilder} to build a Polygon
|
||||
*
|
||||
* @return PolygonBuilder instance
|
||||
*/
|
||||
public static PolygonBuilder newPolygon() {
|
||||
return new PolygonBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MultiPolygonBuilder} to build a MultiPolygon
|
||||
*
|
||||
* @return MultiPolygonBuilder instance
|
||||
*/
|
||||
public static MultiPolygonBuilder newMultiPolygon() {
|
||||
return new MultiPolygonBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given Shape into the JTS {@link Geometry} representation.
|
||||
* If the Shape already uses a Geometry, that is returned.
|
||||
*
|
||||
* @param shape Shape to convert
|
||||
* @return Geometry representation of the Shape
|
||||
*/
|
||||
public static Geometry toJTSGeometry(Shape shape) {
|
||||
if (shape instanceof JtsGeometry) {
|
||||
return ((JtsGeometry) shape).getGeom();
|
||||
} else if (shape instanceof JtsPoint) {
|
||||
return ((JtsPoint) shape).getGeom();
|
||||
} else if (shape instanceof Rectangle) {
|
||||
Rectangle rectangle = (Rectangle) shape;
|
||||
|
||||
if (rectangle.getCrossesDateLine()) {
|
||||
throw new IllegalArgumentException("Cannot convert Rectangles that cross the dateline into JTS Geometrys");
|
||||
}
|
||||
|
||||
return newPolygon().point(rectangle.getMinX(), rectangle.getMaxY())
|
||||
.point(rectangle.getMaxX(), rectangle.getMaxY())
|
||||
.point(rectangle.getMaxX(), rectangle.getMinY())
|
||||
.point(rectangle.getMinX(), rectangle.getMinY())
|
||||
.point(rectangle.getMinX(), rectangle.getMaxY()).toPolygon();
|
||||
} else if (shape instanceof Point) {
|
||||
Point point = (Point) shape;
|
||||
return GEOMETRY_FACTORY.createPoint(new Coordinate(point.getX(), point.getY()));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Shape type [" + shape.getClass().getSimpleName() + "] not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a {@link Rectangle} instance
|
||||
*/
|
||||
public static class RectangleBuilder {
|
||||
|
||||
private Point topLeft;
|
||||
private Point bottomRight;
|
||||
|
||||
/**
|
||||
* Sets the top left point of the Rectangle
|
||||
*
|
||||
* @param lon Longitude of the top left point
|
||||
* @param lat Latitude of the top left point
|
||||
* @return this
|
||||
*/
|
||||
public RectangleBuilder topLeft(double lon, double lat) {
|
||||
this.topLeft = new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bottom right point of the Rectangle
|
||||
*
|
||||
* @param lon Longitude of the bottom right point
|
||||
* @param lat Latitude of the bottom right point
|
||||
* @return this
|
||||
*/
|
||||
public RectangleBuilder bottomRight(double lon, double lat) {
|
||||
this.bottomRight = new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link Rectangle} instance
|
||||
*
|
||||
* @return Built Rectangle
|
||||
*/
|
||||
public Rectangle build() {
|
||||
return new RectangleImpl(topLeft.getX(), bottomRight.getX(), bottomRight.getY(), topLeft.getY(), GeoShapeConstants.SPATIAL_CONTEXT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a {@link Shape} instance of a MultiPolygon
|
||||
*/
|
||||
public static class MultiPolygonBuilder {
|
||||
private final ArrayList<EmbededPolygonBuilder<MultiPolygonBuilder>> polygons = new ArrayList<EmbededPolygonBuilder<MultiPolygonBuilder>>();
|
||||
|
||||
/**
|
||||
* Add a new polygon to the multipolygon
|
||||
*
|
||||
* @return builder for the new polygon
|
||||
*/
|
||||
public EmbededPolygonBuilder<MultiPolygonBuilder> polygon() {
|
||||
EmbededPolygonBuilder<MultiPolygonBuilder> builder = new EmbededPolygonBuilder<MultiPolygonBuilder>(this);
|
||||
polygons.add(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public Shape build() {
|
||||
return new JtsGeometry(toMultiPolygon(), GeoShapeConstants.SPATIAL_CONTEXT, true);
|
||||
}
|
||||
|
||||
public MultiPolygon toMultiPolygon() {
|
||||
Polygon[] polygons = new Polygon[this.polygons.size()];
|
||||
for (int i = 0; i<polygons.length; i++) {
|
||||
polygons[i] = this.polygons.get(i).toPolygon();
|
||||
}
|
||||
return GEOMETRY_FACTORY.createMultiPolygon(polygons);
|
||||
}
|
||||
|
||||
public XContentBuilder toXContent(String name, XContentBuilder xcontent) throws IOException {
|
||||
if(name != null) {
|
||||
xcontent.startObject(name);
|
||||
} else {
|
||||
xcontent.startObject();
|
||||
}
|
||||
xcontent.field("type", "multipolygon");
|
||||
emdedXContent("coordinates", xcontent);
|
||||
xcontent.endObject();
|
||||
return xcontent;
|
||||
}
|
||||
|
||||
protected void emdedXContent(String name, XContentBuilder xcontent) throws IOException {
|
||||
if(name != null) {
|
||||
xcontent.startArray(name);
|
||||
} else {
|
||||
xcontent.startArray();
|
||||
}
|
||||
for(EmbededPolygonBuilder<MultiPolygonBuilder> polygon : polygons) {
|
||||
polygon.emdedXContent(null, xcontent);
|
||||
}
|
||||
xcontent.endArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a {@link Shape} instance of a single Polygon
|
||||
*/
|
||||
public static class PolygonBuilder extends EmbededPolygonBuilder<PolygonBuilder> {
|
||||
|
||||
private PolygonBuilder() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolygonBuilder close() {
|
||||
super.close();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a {@link Shape} instance of a Polygon
|
||||
*/
|
||||
public static class EmbededPolygonBuilder<E> {
|
||||
|
||||
private final E parent;
|
||||
private final LinearRingBuilder<EmbededPolygonBuilder<E>> ring = new LinearRingBuilder<EmbededPolygonBuilder<E>>(this);
|
||||
private final ArrayList<LinearRingBuilder<EmbededPolygonBuilder<E>>> holes = new ArrayList<LinearRingBuilder<EmbededPolygonBuilder<E>>>();
|
||||
|
||||
private EmbededPolygonBuilder(E parent) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a point to the Polygon
|
||||
*
|
||||
* @param lon Longitude of the point
|
||||
* @param lat Latitude of the point
|
||||
* @return this
|
||||
*/
|
||||
public EmbededPolygonBuilder<E> point(double lon, double lat) {
|
||||
ring.point(lon, lat);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start creating a new hole within the polygon
|
||||
* @return a builder for holes
|
||||
*/
|
||||
public LinearRingBuilder<EmbededPolygonBuilder<E>> hole() {
|
||||
LinearRingBuilder<EmbededPolygonBuilder<E>> builder = new LinearRingBuilder<EmbededPolygonBuilder<E>>(this);
|
||||
this.holes.add(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link Shape} instance representing the {@link Polygon}
|
||||
*
|
||||
* @return Built LinearRing
|
||||
*/
|
||||
public Shape build() {
|
||||
return new JtsGeometry(toPolygon(), GeoShapeConstants.SPATIAL_CONTEXT, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the raw {@link Polygon}
|
||||
*
|
||||
* @return Built polygon
|
||||
*/
|
||||
public Polygon toPolygon() {
|
||||
this.ring.close();
|
||||
LinearRing ring = this.ring.toLinearRing();
|
||||
LinearRing[] rings = new LinearRing[holes.size()];
|
||||
for (int i = 0; i < rings.length; i++) {
|
||||
rings[i] = this.holes.get(i).toLinearRing();
|
||||
}
|
||||
return GEOMETRY_FACTORY.createPolygon(ring, rings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the linestring by copying the first point if necessary
|
||||
* @return parent object
|
||||
*/
|
||||
public E close() {
|
||||
this.ring.close();
|
||||
return parent;
|
||||
}
|
||||
|
||||
public XContentBuilder toXContent(String name, XContentBuilder xcontent) throws IOException {
|
||||
if(name != null) {
|
||||
xcontent.startObject(name);
|
||||
} else {
|
||||
xcontent.startObject();
|
||||
}
|
||||
xcontent.field("type", "polygon");
|
||||
emdedXContent("coordinates", xcontent);
|
||||
xcontent.endObject();
|
||||
return xcontent;
|
||||
}
|
||||
|
||||
protected void emdedXContent(String name, XContentBuilder xcontent) throws IOException {
|
||||
if(name != null) {
|
||||
xcontent.startArray(name);
|
||||
} else {
|
||||
xcontent.startArray();
|
||||
}
|
||||
ring.emdedXContent(null, xcontent);
|
||||
for (LinearRingBuilder<?> ring : holes) {
|
||||
ring.emdedXContent(null, xcontent);
|
||||
}
|
||||
xcontent.endArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a {@link Shape} instance of a Polygon
|
||||
*/
|
||||
public static class LinearRingBuilder<E> {
|
||||
|
||||
private final E parent;
|
||||
private final List<Point> points = new ArrayList<Point>();
|
||||
|
||||
private LinearRingBuilder(E parent) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a point to the Ring
|
||||
*
|
||||
* @param lon Longitude of the point
|
||||
* @param lat Latitude of the point
|
||||
* @return this
|
||||
*/
|
||||
public LinearRingBuilder<E> point(double lon, double lat) {
|
||||
points.add(new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link Shape} instance representing the ring
|
||||
*
|
||||
* @return Built LinearRing
|
||||
*/
|
||||
protected Shape build() {
|
||||
return new JtsGeometry(toLinearRing(), GeoShapeConstants.SPATIAL_CONTEXT, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the raw {@link Polygon}
|
||||
*
|
||||
* @return Built LinearRing
|
||||
*/
|
||||
protected LinearRing toLinearRing() {
|
||||
this.close();
|
||||
Coordinate[] coordinates = new Coordinate[points.size()];
|
||||
for (int i = 0; i < coordinates.length; i++) {
|
||||
coordinates[i] = new Coordinate(points.get(i).getX(), points.get(i).getY());
|
||||
}
|
||||
|
||||
return GEOMETRY_FACTORY.createLinearRing(coordinates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the linestring by copying the first point if necessary
|
||||
* @return parent object
|
||||
*/
|
||||
public E close() {
|
||||
Point first = points.get(0);
|
||||
Point last = points.get(points.size()-1);
|
||||
if(first.getX() != last.getX() || first.getY() != last.getY()) {
|
||||
points.add(first);
|
||||
}
|
||||
|
||||
if(points.size()<4) {
|
||||
throw new ElasticSearchIllegalArgumentException("A linear ring is defined by a least four points");
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
public XContentBuilder toXContent(String name, XContentBuilder xcontent) throws IOException {
|
||||
if(name != null) {
|
||||
xcontent.startObject(name);
|
||||
} else {
|
||||
xcontent.startObject();
|
||||
}
|
||||
xcontent.field("type", "linestring");
|
||||
emdedXContent("coordinates", xcontent);
|
||||
xcontent.endObject();
|
||||
return xcontent;
|
||||
}
|
||||
|
||||
protected void emdedXContent(String name, XContentBuilder xcontent) throws IOException {
|
||||
if(name != null) {
|
||||
xcontent.startArray(name);
|
||||
} else {
|
||||
xcontent.startArray();
|
||||
}
|
||||
for(Point point : points) {
|
||||
xcontent.startArray().value(point.getY()).value(point.getX()).endArray();
|
||||
}
|
||||
xcontent.endArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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 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.GeometryFactory;
|
||||
import com.vividsolutions.jts.geom.LineString;
|
||||
|
||||
public abstract class BaseLineStringBuilder<E extends BaseLineStringBuilder<E>> extends PointCollection<E> {
|
||||
|
||||
protected BaseLineStringBuilder() {
|
||||
this(new ArrayList<Coordinate>());
|
||||
}
|
||||
|
||||
protected BaseLineStringBuilder(ArrayList<Coordinate> points) {
|
||||
super(points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return coordinatesToXcontent(builder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape build() {
|
||||
Coordinate[] coordinates = points.toArray(new Coordinate[points.size()]);
|
||||
Geometry geometry;
|
||||
if(wrapdateline) {
|
||||
ArrayList<LineString> strings = decompose(FACTORY, coordinates, new ArrayList<LineString>());
|
||||
|
||||
if(strings.size() == 1) {
|
||||
geometry = strings.get(0);
|
||||
} else {
|
||||
LineString[] linestrings = strings.toArray(new LineString[strings.size()]);
|
||||
geometry = FACTORY.createMultiLineString(linestrings);
|
||||
}
|
||||
|
||||
} else {
|
||||
geometry = FACTORY.createLineString(coordinates);
|
||||
}
|
||||
return new JtsGeometry(geometry, SPATIAL_CONTEXT, !wrapdateline);
|
||||
}
|
||||
|
||||
protected static ArrayList<LineString> decompose(GeometryFactory factory, Coordinate[] coordinates, ArrayList<LineString> strings) {
|
||||
for(Coordinate[] part : decompose(+DATELINE, coordinates)) {
|
||||
for(Coordinate[] line : decompose(-DATELINE, part)) {
|
||||
strings.add(factory.createLineString(line));
|
||||
}
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompose a linestring given as array of coordinates at a vertical line.
|
||||
*
|
||||
* @param dateline x-axis intercept of the vertical line
|
||||
* @param coordinates coordinates forming the linestring
|
||||
* @return array of linestrings given as coordinate arrays
|
||||
*/
|
||||
protected static Coordinate[][] decompose(double dateline, Coordinate[] coordinates) {
|
||||
int offset = 0;
|
||||
ArrayList<Coordinate[]> parts = new ArrayList<Coordinate[]>();
|
||||
|
||||
double shift = coordinates[0].x > DATELINE ? DATELINE : (coordinates[0].x < -DATELINE ? -DATELINE : 0);
|
||||
|
||||
for (int i = 1; i < coordinates.length; i++) {
|
||||
double t = intersection(coordinates[i-1], coordinates[i], dateline);
|
||||
if(!Double.isNaN(t)) {
|
||||
Coordinate[] part;
|
||||
if(t<1) {
|
||||
part = Arrays.copyOfRange(coordinates, offset, i+1);
|
||||
part[part.length-1] = Edge.position(coordinates[i-1], coordinates[i], t);
|
||||
coordinates[offset+i-1] = Edge.position(coordinates[i-1], coordinates[i], t);
|
||||
shift(shift, part);
|
||||
offset = i-1;
|
||||
shift = coordinates[i].x > DATELINE ? DATELINE : (coordinates[i].x < -DATELINE ? -DATELINE : 0);
|
||||
} else {
|
||||
part = shift(shift, Arrays.copyOfRange(coordinates, offset, i+1));
|
||||
offset = i;
|
||||
}
|
||||
parts.add(part);
|
||||
}
|
||||
}
|
||||
|
||||
if(offset == 0) {
|
||||
parts.add(shift(shift, coordinates));
|
||||
} else if(offset < coordinates.length-1) {
|
||||
Coordinate[] part = Arrays.copyOfRange(coordinates, offset, coordinates.length);
|
||||
parts.add(shift(shift, part));
|
||||
}
|
||||
return parts.toArray(new Coordinate[parts.size()][]);
|
||||
}
|
||||
|
||||
private static Coordinate[] shift(double shift, Coordinate...coordinates) {
|
||||
if(shift != 0) {
|
||||
for (int j = 0; j < coordinates.length; j++) {
|
||||
coordinates[j] = new Coordinate(coordinates[j].x - 2 * shift, coordinates[j].y);
|
||||
}
|
||||
}
|
||||
return coordinates;
|
||||
}
|
||||
}
|
@ -0,0 +1,475 @@
|
||||
/*
|
||||
* 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.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.GeometryFactory;
|
||||
import com.vividsolutions.jts.geom.LinearRing;
|
||||
import com.vividsolutions.jts.geom.MultiPolygon;
|
||||
import com.vividsolutions.jts.geom.Polygon;
|
||||
|
||||
/**
|
||||
* The {@link BasePolygonBuilder} implements the groundwork to create polygons. This contains
|
||||
* Methods to wrap polygons at the dateline and building shapes from the data held by the
|
||||
* builder.
|
||||
* Since this Builder can be embedded to other builders (i.e. {@link MultiPolygonBuilder})
|
||||
* the class of the embedding builder is given by the generic argument <code>E</code>
|
||||
|
||||
* @param <E> type of the embedding class
|
||||
*/
|
||||
public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extends ShapeBuilder {
|
||||
|
||||
public static final GeoShapeType TYPE = GeoShapeType.POLYGON;
|
||||
|
||||
// Linear ring defining the shell of the polygon
|
||||
protected Ring<E> shell;
|
||||
|
||||
// List of linear rings defining the holes of the polygon
|
||||
protected final ArrayList<BaseLineStringBuilder<?>> holes = new ArrayList<BaseLineStringBuilder<?>>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private E thisRef() {
|
||||
return (E)this;
|
||||
}
|
||||
|
||||
public E point(double longitude, double latitude) {
|
||||
shell.point(longitude, latitude);
|
||||
return thisRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a point to the shell of the polygon
|
||||
* @param coordinate coordinate of the new point
|
||||
* @return this
|
||||
*/
|
||||
public E point(Coordinate coordinate) {
|
||||
shell.point(coordinate);
|
||||
return thisRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a array of points to the shell of the polygon
|
||||
* @param coordinates coordinates of the new points to add
|
||||
* @return this
|
||||
*/
|
||||
public E points(Coordinate...coordinates) {
|
||||
shell.points(coordinates);
|
||||
return thisRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new hole to the polygon
|
||||
* @param hole linear ring defining the hole
|
||||
* @return this
|
||||
*/
|
||||
public E hole(BaseLineStringBuilder<?> hole) {
|
||||
holes.add(hole);
|
||||
return thisRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* build new hole to the polygon
|
||||
* @param hole linear ring defining the hole
|
||||
* @return this
|
||||
*/
|
||||
public Ring<E> hole() {
|
||||
Ring<E> hole = new Ring<E>(thisRef());
|
||||
this.holes.add(hole);
|
||||
return hole;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the shell of the polygon
|
||||
* @return parent
|
||||
*/
|
||||
public ShapeBuilder close() {
|
||||
return shell.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* The coordinates setup by the builder will be assembled to a polygon. The result will consist of
|
||||
* a set of polygons. Each of these components holds a list of linestrings defining the polygon: the
|
||||
* first set of coordinates will be used as the shell of the polygon. The others are defined to holes
|
||||
* within the polygon.
|
||||
* This Method also wraps the polygons at the dateline. In order to this fact the result may
|
||||
* contains more polygons and less holes than defined in the builder it self.
|
||||
*
|
||||
* @return coordinates of the polygon
|
||||
*/
|
||||
public Coordinate[][][] coordinates() {
|
||||
int numEdges = shell.points.size()-1; // Last point is repeated
|
||||
for (int i = 0; i < holes.size(); i++) {
|
||||
numEdges += holes.get(i).points.size()-1;
|
||||
}
|
||||
|
||||
Edge[] edges = new Edge[numEdges];
|
||||
Edge[] holeComponents = new Edge[holes.size()];
|
||||
|
||||
int offset = createEdges(0, true, shell, edges, 0);
|
||||
for (int i = 0; i < holes.size(); i++) {
|
||||
int length = createEdges(i+1, false, this.holes.get(i), edges, offset);
|
||||
holeComponents[i] = edges[offset];
|
||||
offset += length;
|
||||
}
|
||||
|
||||
int numHoles = holeComponents.length;
|
||||
|
||||
numHoles = merge(edges, 0, intersections(+DATELINE, edges), holeComponents, numHoles);
|
||||
numHoles = merge(edges, 0, intersections(-DATELINE, edges), holeComponents, numHoles);
|
||||
|
||||
return compose(edges, holeComponents, numHoles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape build() {
|
||||
Geometry geometry = buildGeometry(FACTORY, wrapdateline);
|
||||
return new JtsGeometry(geometry, SPATIAL_CONTEXT, !wrapdateline);
|
||||
}
|
||||
|
||||
protected XContentBuilder coordinatesArray(XContentBuilder builder, Params params) throws IOException {
|
||||
shell.coordinatesToXcontent(builder, true);
|
||||
for(BaseLineStringBuilder<?> hole : holes) {
|
||||
hole.coordinatesToXcontent(builder, true);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field(FIELD_TYPE, TYPE.shapename);
|
||||
builder.startArray(FIELD_COORDINATES);
|
||||
coordinatesArray(builder, params);
|
||||
builder.endArray();
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public Geometry buildGeometry(GeometryFactory factory, boolean fixDateline) {
|
||||
if(fixDateline) {
|
||||
Coordinate[][][] polygons = coordinates();
|
||||
return polygons.length == 1
|
||||
? polygon(factory, polygons[0])
|
||||
: multipolygon(factory, polygons);
|
||||
} else {
|
||||
return toPolygon(factory);
|
||||
}
|
||||
}
|
||||
|
||||
public Polygon toPolygon() {
|
||||
return toPolygon(FACTORY);
|
||||
}
|
||||
|
||||
protected Polygon toPolygon(GeometryFactory factory) {
|
||||
final LinearRing shell = linearRing(factory, this.shell.points);
|
||||
final LinearRing[] holes = new LinearRing[this.holes.size()];
|
||||
Iterator<BaseLineStringBuilder<?>> iterator = this.holes.iterator();
|
||||
for (int i = 0; iterator.hasNext(); i++) {
|
||||
holes[i] = linearRing(factory, iterator.next().points);
|
||||
}
|
||||
return factory.createPolygon(shell, holes);
|
||||
}
|
||||
|
||||
protected static LinearRing linearRing(GeometryFactory factory, ArrayList<Coordinate> coordinates) {
|
||||
return factory.createLinearRing(coordinates.toArray(new Coordinate[coordinates.size()]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoShapeType type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
protected static Polygon polygon(GeometryFactory factory, Coordinate[][] polygon) {
|
||||
LinearRing shell = factory.createLinearRing(polygon[0]);
|
||||
LinearRing[] holes;
|
||||
|
||||
if(polygon.length > 1) {
|
||||
holes = new LinearRing[polygon.length-1];
|
||||
for (int i = 0; i < holes.length; i++) {
|
||||
holes[i] = factory.createLinearRing(polygon[i+1]);
|
||||
}
|
||||
} else {
|
||||
holes = null;
|
||||
}
|
||||
return factory.createPolygon(shell, holes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Multipolygon from a set of coordinates. Each primary array contains a polygon which
|
||||
* in turn contains an array of linestrings. These line Strings are represented as an array of
|
||||
* coordinates. The first linestring will be the shell of the polygon the others define holes
|
||||
* within the polygon.
|
||||
*
|
||||
* @param factory {@link GeometryFactory} to use
|
||||
* @param polygons definition of polygons
|
||||
* @return a new Multipolygon
|
||||
*/
|
||||
protected static MultiPolygon multipolygon(GeometryFactory factory, Coordinate[][][] polygons) {
|
||||
Polygon[] polygonSet = new Polygon[polygons.length];
|
||||
for (int i = 0; i < polygonSet.length; i++) {
|
||||
polygonSet[i] = polygon(factory, polygons[i]);
|
||||
}
|
||||
return factory.createMultiPolygon(polygonSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the component id of all edges in a ring to a given id and shifts the
|
||||
* coordinates of this component according to the dateline
|
||||
*
|
||||
* @param edge An arbitrary edge of the component
|
||||
* @param id id to apply to the component
|
||||
* @param edges a list of edges to which all edges of the component will be added (could be <code>null</code>)
|
||||
* @return number of edges that belong to this component
|
||||
*/
|
||||
private static int component(final Edge edge, final int id, final ArrayList<Edge> edges) {
|
||||
// find a coordinate that is not part of the dateline
|
||||
Edge any = edge;
|
||||
while(any.coordinate.x == +DATELINE || any.coordinate.x == -DATELINE) {
|
||||
if((any = any.next) == edge) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double shift = any.coordinate.x > DATELINE ? DATELINE : (any.coordinate.x < -DATELINE ? -DATELINE : 0);
|
||||
if (debugEnabled()) {
|
||||
LOGGER.debug("shift: {[]}", shift);
|
||||
}
|
||||
|
||||
// run along the border of the component, collect the
|
||||
// edges, shift them according to the dateline and
|
||||
// update the component id
|
||||
int length = 0;
|
||||
Edge current = edge;
|
||||
do {
|
||||
|
||||
current.coordinate = shift(current.coordinate, shift);
|
||||
current.component = id;
|
||||
if(edges != null) {
|
||||
edges.add(current);
|
||||
}
|
||||
|
||||
length++;
|
||||
} while((current = current.next) != edge);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute all coordinates of a component
|
||||
* @param component an arbitrary edge of the component
|
||||
* @param coordinates Array of coordinates to write the result to
|
||||
* @return the coordinates parameter
|
||||
*/
|
||||
private static Coordinate[] coordinates(Edge component, Coordinate[] coordinates) {
|
||||
for (int i = 0; i < coordinates.length; i++) {
|
||||
coordinates[i] = (component = component.next).coordinate;
|
||||
}
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
private static Coordinate[][][] buildCoordinates(ArrayList<ArrayList<Coordinate[]>> components) {
|
||||
Coordinate[][][] result = new Coordinate[components.size()][][];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
ArrayList<Coordinate[]> component = components.get(i);
|
||||
result[i] = component.toArray(new Coordinate[component.size()][]);
|
||||
}
|
||||
|
||||
if(debugEnabled()) {
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
LOGGER.debug("Component {[]}:", i);
|
||||
for (int j = 0; j < result[i].length; j++) {
|
||||
LOGGER.debug("\t" + Arrays.toString(result[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final Coordinate[][] EMPTY = new Coordinate[0][];
|
||||
|
||||
private static Coordinate[][] holes(Edge[] holes, int numHoles) {
|
||||
if (numHoles == 0) {
|
||||
return EMPTY;
|
||||
}
|
||||
final Coordinate[][] points = new Coordinate[numHoles][];
|
||||
|
||||
for (int i = 0; i < numHoles; i++) {
|
||||
int length = component(holes[i], -(i+1), null); // mark as visited by inverting the sign
|
||||
points[i] = coordinates(holes[i], new Coordinate[length+1]);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private static Edge[] edges(Edge[] edges, int numHoles, ArrayList<ArrayList<Coordinate[]>> components) {
|
||||
ArrayList<Edge> mainEdges = new ArrayList<Edge>(edges.length);
|
||||
|
||||
for (int i = 0; i < edges.length; i++) {
|
||||
if (edges[i].component >= 0) {
|
||||
int length = component(edges[i], -(components.size()+numHoles+1), mainEdges);
|
||||
ArrayList<Coordinate[]> component = new ArrayList<Coordinate[]>();
|
||||
component.add(coordinates(edges[i], new Coordinate[length+1]));
|
||||
components.add(component);
|
||||
}
|
||||
}
|
||||
|
||||
return mainEdges.toArray(new Edge[mainEdges.size()]);
|
||||
}
|
||||
|
||||
private static Coordinate[][][] compose(Edge[] edges, Edge[] holes, int numHoles) {
|
||||
final ArrayList<ArrayList<Coordinate[]>> components = new ArrayList<ArrayList<Coordinate[]>>();
|
||||
assign(holes, holes(holes, numHoles), numHoles, edges(edges, numHoles, components), components);
|
||||
return buildCoordinates(components);
|
||||
}
|
||||
|
||||
private static void assign(Edge[] holes, Coordinate[][] points, int numHoles, Edge[] edges, ArrayList<ArrayList<Coordinate[]>> 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<P extends ShapeBuilder> extends BaseLineStringBuilder<Ring<P>> {
|
||||
|
||||
private final P parent;
|
||||
|
||||
protected Ring(P parent) {
|
||||
this(parent, new ArrayList<Coordinate>());
|
||||
}
|
||||
|
||||
protected Ring(P parent, ArrayList<Coordinate> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<LineStringBuilder> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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<BaseLineStringBuilder<?>> lines = new ArrayList<BaseLineStringBuilder<?>>();
|
||||
|
||||
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<LineString> parts = new ArrayList<LineString>();
|
||||
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<BaseLineStringBuilder<?>> 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<InternalLineStringBuilder> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<MultiPointBuilder> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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<BasePolygonBuilder<?>> polygons = new ArrayList<BasePolygonBuilder<?>>();
|
||||
|
||||
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<Polygon> polygonSet = new ArrayList<Polygon>(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<BasePolygonBuilder<?>> 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<InternalPolygonBuilder> {
|
||||
|
||||
private final MultiPolygonBuilder collection;
|
||||
|
||||
private InternalPolygonBuilder(MultiPolygonBuilder collection) {
|
||||
super();
|
||||
this.collection = collection;
|
||||
this.shell = new Ring<InternalPolygonBuilder>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiPolygonBuilder close() {
|
||||
super.close();
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<E extends PointCollection<E>> extends ShapeBuilder {
|
||||
|
||||
protected final ArrayList<Coordinate> points;
|
||||
|
||||
protected PointCollection() {
|
||||
this(new ArrayList<Coordinate>());
|
||||
}
|
||||
|
||||
protected PointCollection(ArrayList<Coordinate> 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;
|
||||
}
|
||||
}
|
@ -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<PolygonBuilder> {
|
||||
|
||||
public PolygonBuilder() {
|
||||
this(new ArrayList<Coordinate>());
|
||||
}
|
||||
|
||||
protected PolygonBuilder(ArrayList<Coordinate> points) {
|
||||
super();
|
||||
this.shell = new Ring<PolygonBuilder>(this, points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolygonBuilder close() {
|
||||
super.close();
|
||||
return this;
|
||||
}
|
||||
}
|
@ -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<CoordinateNode> nodes = new ArrayList<CoordinateNode>();
|
||||
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 <code><null</code>
|
||||
* @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.
|
||||
* <p/>
|
||||
* 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<CoordinateNode> 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<CoordinateNode> 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<Edge> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String> {
|
||||
|
||||
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<String> {
|
||||
@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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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("");
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<Coordinate> 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<Coordinate> 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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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<Coordinate>();
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Coordinate> lineCoordinates = new ArrayList<Coordinate>();
|
||||
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<Coordinate> shellCoordinates = new ArrayList<Coordinate>();
|
||||
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<Coordinate> shellCoordinates = new ArrayList<Coordinate>();
|
||||
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<Coordinate> holeCoordinates = new ArrayList<Coordinate>();
|
||||
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<Coordinate> multiPointCoordinates = new ArrayList<Coordinate>();
|
||||
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<Coordinate> shellCoordinates = new ArrayList<Coordinate>();
|
||||
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<Coordinate> holeCoordinates = new ArrayList<Coordinate>();
|
||||
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<Coordinate>();
|
||||
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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user