[Geo] Decouple geojson parse logic from ShapeBuilders

This is the first step to supporting WKT (and other future) format(s). The ShapeBuilders are quite messy and can be simplified by decoupling the parse logic from the build logic. This commit refactors the parsing logic into its own package separate from the Shape builders. It also decouples the GeoShapeType into a standalone enumerator that is responsible for validating the parsed data and providing the appropriate builder. This future-proofs the code making it easier to maintain and add new shape types.
This commit is contained in:
Nicholas Knize 2017-11-01 12:29:52 -05:00
parent 3ed558d718
commit 8904fc8210
34 changed files with 1058 additions and 965 deletions

View File

@ -23,7 +23,7 @@ import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.common.geo.builders.MultiPointBuilder;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.GeoShapeQueryBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
@ -189,7 +189,7 @@ public class QueryDSLDocumentationTests extends ESTestCase {
// tag::geo_shape
GeoShapeQueryBuilder qb = geoShapeQuery(
"pin.location", // <1>
ShapeBuilders.newMultiPoint( // <2>
new MultiPointBuilder( // <2>
new CoordinatesBuilder()
.coordinate(0, 0)
.coordinate(0, 10)

View File

@ -0,0 +1,316 @@
/*
* Licensed to Elasticsearch 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.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.geo.builders.CircleBuilder;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
import org.elasticsearch.common.geo.builders.LineStringBuilder;
import org.elasticsearch.common.geo.builders.MultiLineStringBuilder;
import org.elasticsearch.common.geo.builders.MultiPointBuilder;
import org.elasticsearch.common.geo.builders.MultiPolygonBuilder;
import org.elasticsearch.common.geo.builders.PointBuilder;
import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
import org.elasticsearch.common.geo.parsers.CoordinateNode;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
import org.elasticsearch.common.unit.DistanceUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Enumeration that lists all {@link GeoShapeType}s that can be parsed and indexed
*/
public enum GeoShapeType {
POINT("point") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
return new PointBuilder().coordinate(validate(coordinates, coerce).coordinate);
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
if (coordinates.isEmpty()) {
throw new ElasticsearchParseException(
"invalid number of points (0) provided when expecting a single coordinate ([lat, lng])");
} else if (coordinates.children != null) {
throw new ElasticsearchParseException("multipoint data provided when single point data expected.");
}
return coordinates;
}
},
MULTIPOINT("multipoint") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
validate(coordinates, coerce);
CoordinatesBuilder coordinatesBuilder = new CoordinatesBuilder();
for (CoordinateNode node : coordinates.children) {
coordinatesBuilder.coordinate(node.coordinate);
}
return new MultiPointBuilder(coordinatesBuilder.build());
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
if (coordinates.children == null || coordinates.children.isEmpty()) {
if (coordinates.coordinate != null) {
throw new ElasticsearchParseException("single coordinate found when expecting an array of " +
"coordinates. change type to point or change data to an array of >0 coordinates");
}
throw new ElasticsearchParseException("no data provided for multipoint object when expecting " +
">0 points (e.g., [[lat, lng]] or [[lat, lng], ...])");
} else {
for (CoordinateNode point : coordinates.children) {
POINT.validate(point, coerce);
}
}
return coordinates;
}
},
LINESTRING("linestring") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
validate(coordinates, coerce);
CoordinatesBuilder line = new CoordinatesBuilder();
for (CoordinateNode node : coordinates.children) {
line.coordinate(node.coordinate);
}
return new LineStringBuilder(line);
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
if (coordinates.children.size() < 2) {
throw new ElasticsearchParseException("invalid number of points in LineString (found [{}] - must be >= 2)",
coordinates.children.size());
}
return coordinates;
}
},
MULTILINESTRING("multilinestring") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
validate(coordinates, coerce);
MultiLineStringBuilder multiline = new MultiLineStringBuilder();
for (CoordinateNode node : coordinates.children) {
multiline.linestring(LineStringBuilder.class.cast(LINESTRING.getBuilder(node, radius, orientation, coerce)));
}
return multiline;
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
if (coordinates.children.size() < 1) {
throw new ElasticsearchParseException("invalid number of lines in MultiLineString (found [{}] - must be >= 1)",
coordinates.children.size());
}
return coordinates;
}
},
POLYGON("polygon") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
validate(coordinates, coerce);
// build shell
LineStringBuilder shell = LineStringBuilder.class.cast(LINESTRING.getBuilder(coordinates.children.get(0),
radius, orientation, coerce));
// build polygon with shell and holes
PolygonBuilder polygon = new PolygonBuilder(shell, orientation);
for (int i = 1; i < coordinates.children.size(); ++i) {
CoordinateNode child = coordinates.children.get(i);
LineStringBuilder hole = LineStringBuilder.class.cast(LINESTRING.getBuilder(child, radius, orientation, coerce));
polygon.hole(hole);
}
return polygon;
}
void validateLinearRing(CoordinateNode coordinates, boolean coerce) {
if (coordinates.children == null || coordinates.children.isEmpty()) {
String error = "Invalid LinearRing found.";
error += (coordinates.coordinate == null) ?
" No coordinate array provided" : " Found a single coordinate when expecting a coordinate array";
throw new ElasticsearchParseException(error);
}
int numValidPts = coerce ? 3 : 4;
if (coordinates.children.size() < numValidPts) {
throw new ElasticsearchParseException("invalid number of points in LinearRing (found [{}] - must be >= [{}])",
coordinates.children.size(), numValidPts);
}
// close linear ring iff coerce is set and ring is open, otherwise throw parse exception
if (!coordinates.children.get(0).coordinate.equals(
coordinates.children.get(coordinates.children.size() - 1).coordinate)) {
if (coerce == true) {
coordinates.children.add(coordinates.children.get(0));
} else {
throw new ElasticsearchParseException("invalid LinearRing found (coordinates are not closed)");
}
}
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
/**
* Per GeoJSON spec (http://geojson.org/geojson-spec.html#linestring)
* A LinearRing is closed LineString with 4 or more positions. The first and last positions
* are equivalent (they represent equivalent points). Though a LinearRing is not explicitly
* represented as a GeoJSON geometry type, it is referred to in the Polygon geometry type definition.
*/
if (coordinates.children == null || coordinates.children.isEmpty()) {
throw new ElasticsearchParseException(
"invalid LinearRing provided for type polygon. Linear ring must be an array of coordinates");
}
for (CoordinateNode ring : coordinates.children) {
validateLinearRing(ring, coerce);
}
return coordinates;
}
},
MULTIPOLYGON("multipolygon") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
validate(coordinates, coerce);
MultiPolygonBuilder polygons = new MultiPolygonBuilder(orientation);
for (CoordinateNode node : coordinates.children) {
polygons.polygon(PolygonBuilder.class.cast(POLYGON.getBuilder(node, radius, orientation, coerce)));
}
return polygons;
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
// noop; todo validate at least 1 polygon to ensure valid multipolygon
return coordinates;
}
},
ENVELOPE("envelope") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
validate(coordinates, coerce);
// verify coordinate bounds, correct if necessary
Coordinate uL = coordinates.children.get(0).coordinate;
Coordinate lR = coordinates.children.get(1).coordinate;
if (((lR.x < uL.x) || (uL.y < lR.y))) {
Coordinate uLtmp = uL;
uL = new Coordinate(Math.min(uL.x, lR.x), Math.max(uL.y, lR.y));
lR = new Coordinate(Math.max(uLtmp.x, lR.x), Math.min(uLtmp.y, lR.y));
}
return new EnvelopeBuilder(uL, lR);
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
// validate the coordinate array for envelope type
if (coordinates.children.size() != 2) {
throw new ElasticsearchParseException(
"invalid number of points [{}] provided for geo_shape [{}] when expecting an array of 2 coordinates",
coordinates.children.size(), GeoShapeType.ENVELOPE.shapename);
}
return coordinates;
}
},
CIRCLE("circle") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
return new CircleBuilder().center(coordinates.coordinate).radius(radius);
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
// noop
return coordinates;
}
},
GEOMETRYCOLLECTION("geometrycollection") {
@Override
public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
Orientation orientation, boolean coerce) {
// noop, handled in parser
return null;
}
@Override
CoordinateNode validate(CoordinateNode coordinates, boolean coerce) {
// noop
return null;
}
};
private final String shapename;
private static Map<String, GeoShapeType> shapeTypeMap = new HashMap<>();
static {
for (GeoShapeType type : values()) {
shapeTypeMap.put(type.shapename, type);
}
}
GeoShapeType(String shapename) {
this.shapename = shapename;
}
public String shapeName() {
return shapename;
}
public static GeoShapeType forName(String geoshapename) {
String typename = geoshapename.toLowerCase(Locale.ROOT);
if (shapeTypeMap.containsKey(typename)) {
return shapeTypeMap.get(typename);
}
throw new IllegalArgumentException("unknown geo_shape ["+geoshapename+"]");
}
public abstract ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius,
ShapeBuilder.Orientation orientation, boolean coerce);
abstract CoordinateNode validate(CoordinateNode coordinates, boolean coerce);
public static List<Entry> getShapeWriteables() {
List<Entry> namedWriteables = new ArrayList<>();
namedWriteables.add(new Entry(ShapeBuilder.class, PointBuilder.TYPE.shapeName(), PointBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, CircleBuilder.TYPE.shapeName(), CircleBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, EnvelopeBuilder.TYPE.shapeName(), EnvelopeBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, MultiPointBuilder.TYPE.shapeName(), MultiPointBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, LineStringBuilder.TYPE.shapeName(), LineStringBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, MultiLineStringBuilder.TYPE.shapeName(), MultiLineStringBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, PolygonBuilder.TYPE.shapeName(), PolygonBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, MultiPolygonBuilder.TYPE.shapeName(), MultiPolygonBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, GeometryCollectionBuilder.TYPE.shapeName(), GeometryCollectionBuilder::new));
return namedWriteables;
}
}

View File

@ -19,6 +19,9 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.locationtech.spatial4j.shape.Circle;
import com.vividsolutions.jts.geom.Coordinate;
@ -31,9 +34,9 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Objects;
public class CircleBuilder extends ShapeBuilder {
public class CircleBuilder extends ShapeBuilder<Circle, CircleBuilder> {
public static final String FIELD_RADIUS = "radius";
public static final ParseField FIELD_RADIUS = new ParseField("radius");
public static final GeoShapeType TYPE = GeoShapeType.CIRCLE;
private DistanceUnit unit = DistanceUnit.DEFAULT;
@ -148,9 +151,9 @@ public class CircleBuilder extends ShapeBuilder {
@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);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.field(FIELD_RADIUS.getPreferredName(), unit.toString(radius));
builder.field(ShapeParser.FIELD_COORDINATES.getPreferredName());
toXContent(builder, center);
return builder.endObject();
}

View File

@ -1,155 +0,0 @@
/*
* Licensed to Elasticsearch 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.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* The {@link CoordinateCollection} is an abstract base implementation for {@link LineStringBuilder} and {@link MultiPointBuilder}.
* It holds a common list of {@link Coordinate}, provides setters for adding elements to the list and can render this to XContent.
*/
public abstract class CoordinateCollection<E extends CoordinateCollection<E>> extends ShapeBuilder {
protected final List<Coordinate> coordinates;
/**
* Construct a new collection of coordinates.
* @param coordinates an initial list of coordinates
* @throws IllegalArgumentException if coordinates is <tt>null</tt> or empty
*/
protected CoordinateCollection(List<Coordinate> coordinates) {
if (coordinates == null || coordinates.size() == 0) {
throw new IllegalArgumentException("cannot create point collection with empty set of points");
}
this.coordinates = coordinates;
}
/**
* Read from a stream.
*/
protected CoordinateCollection(StreamInput in) throws IOException {
int size = in.readVInt();
coordinates = new ArrayList<>(size);
for (int i=0; i < size; i++) {
coordinates.add(readFromStream(in));
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(coordinates.size());
for (Coordinate point : coordinates) {
writeCoordinateTo(point, out);
}
}
@SuppressWarnings("unchecked")
private E thisRef() {
return (E)this;
}
/**
* Add a new coordinate to the collection
* @param longitude longitude of the coordinate
* @param latitude latitude of the coordinate
* @return this
*/
public E coordinate(double longitude, double latitude) {
return this.coordinate(new Coordinate(longitude, latitude));
}
/**
* Add a new coordinate to the collection
* @param coordinate coordinate of the point
* @return this
*/
public E coordinate(Coordinate coordinate) {
this.coordinates.add(coordinate);
return thisRef();
}
/**
* Add a array of coordinates to the collection
*
* @param coordinates array of {@link Coordinate}s to add
* @return this
*/
public E coordinates(Coordinate...coordinates) {
return this.coordinates(Arrays.asList(coordinates));
}
/**
* Add a collection of coordinates to the collection
*
* @param coordinates array of {@link Coordinate}s to add
* @return this
*/
public E coordinates(Collection<? extends Coordinate> coordinates) {
this.coordinates.addAll(coordinates);
return thisRef();
}
/**
* Copy all coordinate 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 = coordinates.toArray(new Coordinate[coordinates.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
*/
protected XContentBuilder coordinatesToXcontent(XContentBuilder builder, boolean closed) throws IOException {
builder.startArray();
for(Coordinate coord : coordinates) {
toXContent(builder, coord);
}
if(closed) {
Coordinate start = coordinates.get(0);
Coordinate end = coordinates.get(coordinates.size()-1);
if(start.x != end.x || start.y != end.y) {
toXContent(builder, coordinates.get(0));
}
}
builder.endArray();
return builder;
}
}

View File

@ -19,6 +19,8 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.locationtech.spatial4j.shape.Rectangle;
import com.vividsolutions.jts.geom.Coordinate;
@ -29,7 +31,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Objects;
public class EnvelopeBuilder extends ShapeBuilder {
public class EnvelopeBuilder extends ShapeBuilder<Rectangle, EnvelopeBuilder> {
public static final GeoShapeType TYPE = GeoShapeType.ENVELOPE;
@ -71,8 +73,8 @@ public class EnvelopeBuilder extends ShapeBuilder {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FIELD_TYPE, TYPE.shapeName());
builder.startArray(FIELD_COORDINATES);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.startArray(ShapeParser.FIELD_COORDINATES.getPreferredName());
toXContent(builder, topLeft);
toXContent(builder, bottomRight);
builder.endArray();

View File

@ -19,6 +19,8 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.locationtech.spatial4j.shape.Shape;
import org.elasticsearch.ElasticsearchException;
@ -125,8 +127,8 @@ public class GeometryCollectionBuilder extends ShapeBuilder {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FIELD_TYPE, TYPE.shapeName());
builder.startArray(FIELD_GEOMETRIES);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.startArray(ShapeParser.FIELD_GEOMETRIES.getPreferredName());
for (ShapeBuilder shape : shapes) {
shape.toXContent(builder, params);
}

View File

@ -24,17 +24,18 @@ import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class LineStringBuilder extends CoordinateCollection<LineStringBuilder> {
public class LineStringBuilder extends ShapeBuilder<JtsGeometry, LineStringBuilder> {
public static final GeoShapeType TYPE = GeoShapeType.LINESTRING;
/**
@ -65,8 +66,8 @@ public class LineStringBuilder extends CoordinateCollection<LineStringBuilder> {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FIELD_TYPE, TYPE.shapeName());
builder.field(FIELD_COORDINATES);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.field(ShapeParser.FIELD_COORDINATES.getPreferredName());
coordinatesToXcontent(builder, false);
builder.endObject();
return builder;
@ -91,7 +92,7 @@ public class LineStringBuilder extends CoordinateCollection<LineStringBuilder> {
}
@Override
public Shape build() {
public JtsGeometry build() {
Coordinate[] coordinates = this.coordinates.toArray(new Coordinate[this.coordinates.size()]);
Geometry geometry;
if(wrapdateline) {
@ -168,21 +169,4 @@ public class LineStringBuilder extends CoordinateCollection<LineStringBuilder> {
}
return coordinates;
}
@Override
public int hashCode() {
return Objects.hash(coordinates);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LineStringBuilder other = (LineStringBuilder) obj;
return Objects.equals(coordinates, other.coordinates);
}
}

View File

@ -19,6 +19,8 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.locationtech.spatial4j.shape.Shape;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
@ -27,21 +29,19 @@ import com.vividsolutions.jts.geom.LineString;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
public class MultiLineStringBuilder extends ShapeBuilder {
public class MultiLineStringBuilder extends ShapeBuilder<JtsGeometry, MultiLineStringBuilder> {
public static final GeoShapeType TYPE = GeoShapeType.MULTILINESTRING;
private final ArrayList<LineStringBuilder> lines = new ArrayList<>();
public MultiLineStringBuilder() {
}
/**
* Read from a stream.
*/
@ -52,6 +52,10 @@ public class MultiLineStringBuilder extends ShapeBuilder {
}
}
public MultiLineStringBuilder() {
super();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(lines.size());
@ -81,8 +85,8 @@ public class MultiLineStringBuilder extends ShapeBuilder {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FIELD_TYPE, TYPE.shapeName());
builder.field(FIELD_COORDINATES);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.field(ShapeParser.FIELD_COORDINATES.getPreferredName());
builder.startArray();
for(LineStringBuilder line : lines) {
line.coordinatesToXcontent(builder, false);
@ -93,7 +97,7 @@ public class MultiLineStringBuilder extends ShapeBuilder {
}
@Override
public Shape build() {
public JtsGeometry build() {
final Geometry geometry;
if(wrapdateline) {
ArrayList<LineString> parts = new ArrayList<>();

View File

@ -21,7 +21,9 @@ package org.elasticsearch.common.geo.builders;
import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.XShapeCollection;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.locationtech.spatial4j.shape.Point;
@ -32,7 +34,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class MultiPointBuilder extends CoordinateCollection<MultiPointBuilder> {
public class MultiPointBuilder extends ShapeBuilder<XShapeCollection<Point>, MultiPointBuilder> {
public static final GeoShapeType TYPE = GeoShapeType.MULTIPOINT;
@ -54,15 +56,15 @@ public class MultiPointBuilder extends CoordinateCollection<MultiPointBuilder> {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FIELD_TYPE, TYPE.shapeName());
builder.field(FIELD_COORDINATES);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.field(ShapeParser.FIELD_COORDINATES.getPreferredName());
super.coordinatesToXcontent(builder, false);
builder.endObject();
return builder;
}
@Override
public Shape build() {
public XShapeCollection<Point> build() {
//Could wrap JtsGeometry but probably slower due to conversions to/from JTS in relate()
//MultiPoint geometry = FACTORY.createMultiPoint(points.toArray(new Coordinate[points.size()]));
List<Point> shapes = new ArrayList<>(coordinates.size());
@ -78,21 +80,4 @@ public class MultiPointBuilder extends CoordinateCollection<MultiPointBuilder> {
public GeoShapeType type() {
return TYPE;
}
@Override
public int hashCode() {
return Objects.hash(coordinates);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MultiPointBuilder other = (MultiPointBuilder) obj;
return Objects.equals(coordinates, other.coordinates);
}
}

View File

@ -19,6 +19,8 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.locationtech.spatial4j.shape.Shape;
import com.vividsolutions.jts.geom.Coordinate;
@ -102,9 +104,9 @@ public class MultiPolygonBuilder extends ShapeBuilder {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FIELD_TYPE, TYPE.shapeName());
builder.field(FIELD_ORIENTATION, orientation.name().toLowerCase(Locale.ROOT));
builder.startArray(FIELD_COORDINATES);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.field(ShapeParser.FIELD_ORIENTATION.getPreferredName(), orientation.name().toLowerCase(Locale.ROOT));
builder.startArray(ShapeParser.FIELD_COORDINATES.getPreferredName());
for(PolygonBuilder polygon : polygons) {
builder.startArray();
polygon.coordinatesArray(builder, params);

View File

@ -19,86 +19,78 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.locationtech.spatial4j.shape.Point;
import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Objects;
import java.util.ArrayList;
public class PointBuilder extends ShapeBuilder {
public class PointBuilder extends ShapeBuilder<Point, PointBuilder> {
public static final GeoShapeType TYPE = GeoShapeType.POINT;
private Coordinate coordinate;
/**
* Create a point at [0.0,0.0]
*/
public PointBuilder() {
this.coordinate = ZERO_ZERO;
super();
this.coordinates.add(ZERO_ZERO);
}
public PointBuilder(double lon, double lat) {
//super(new ArrayList<>(1));
super();
this.coordinates.add(new Coordinate(lon, lat));
}
/**
* Read from a stream.
*/
public PointBuilder(StreamInput in) throws IOException {
coordinate = readFromStream(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
writeCoordinateTo(coordinate, out);
super(in);
}
public PointBuilder coordinate(Coordinate coordinate) {
this.coordinate = coordinate;
this.coordinates.set(0, coordinate);
return this;
}
public double longitude() {
return coordinate.x;
return coordinates.get(0).x;
}
public double latitude() {
return coordinate.y;
return coordinates.get(0).y;
}
/**
* 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 new PointBuilder().coordinate(new Coordinate(longitude, latitude));
}
@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);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.field(ShapeParser.FIELD_COORDINATES.getPreferredName());
toXContent(builder, coordinates.get(0));
return builder.endObject();
}
@Override
public Point build() {
return SPATIAL_CONTEXT.makePoint(coordinate.x, coordinate.y);
return SPATIAL_CONTEXT.makePoint(coordinates.get(0).x, coordinates.get(0).y);
}
@Override
public GeoShapeType type() {
return TYPE;
}
@Override
public int hashCode() {
return Objects.hash(coordinate);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PointBuilder other = (PointBuilder) obj;
return Objects.equals(coordinate, other.coordinate);
}
}

View File

@ -26,12 +26,15 @@ import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import java.io.IOException;
import java.util.ArrayList;
@ -49,7 +52,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* Methods to wrap polygons at the dateline and building shapes from the data held by the
* builder.
*/
public class PolygonBuilder extends ShapeBuilder {
public class PolygonBuilder extends ShapeBuilder<JtsGeometry, PolygonBuilder> {
public static final GeoShapeType TYPE = GeoShapeType.POLYGON;
@ -222,7 +225,7 @@ public class PolygonBuilder extends ShapeBuilder {
}
@Override
public Shape build() {
public JtsGeometry build() {
return jtsGeometry(buildGeometry(FACTORY, wrapdateline));
}
@ -237,9 +240,9 @@ public class PolygonBuilder extends ShapeBuilder {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FIELD_TYPE, TYPE.shapeName());
builder.field(FIELD_ORIENTATION, orientation.name().toLowerCase(Locale.ROOT));
builder.startArray(FIELD_COORDINATES);
builder.field(ShapeParser.FIELD_TYPE.getPreferredName(), TYPE.shapeName());
builder.field(ShapeParser.FIELD_ORIENTATION.getPreferredName(), orientation.name().toLowerCase(Locale.ROOT));
builder.startArray(ShapeParser.FIELD_COORDINATES.getPreferredName());
coordinatesArray(builder, params);
builder.endArray();
builder.endObject();

View File

@ -25,18 +25,14 @@ import com.vividsolutions.jts.geom.GeometryFactory;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Assertions;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.unit.DistanceUnit.Distance;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Shape;
@ -45,14 +41,16 @@ import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* Basic class for building GeoJSON shapes like Polygons, Linestrings, etc
*/
public abstract class ShapeBuilder implements NamedWriteable, ToXContentObject {
public abstract class ShapeBuilder<T extends Shape, E extends ShapeBuilder<T,E>> implements NamedWriteable, ToXContentObject {
protected static final Logger LOGGER = ESLoggerFactory.getLogger(ShapeBuilder.class.getName());
@ -63,6 +61,8 @@ public abstract class ShapeBuilder implements NamedWriteable, ToXContentObject {
DEBUG = Assertions.ENABLED;
}
protected final List<Coordinate> coordinates;
public static final double DATELINE = 180;
/**
@ -85,7 +85,103 @@ public abstract class ShapeBuilder implements NamedWriteable, ToXContentObject {
/** @see org.locationtech.spatial4j.shape.jts.JtsGeometry#index() */
protected static final boolean AUTO_INDEX_JTS_GEOMETRY = true;//may want to turn off once SpatialStrategy impls do it.
/** default ctor */
protected ShapeBuilder() {
coordinates = new ArrayList<>();
}
/** ctor from list of coordinates */
protected ShapeBuilder(List<Coordinate> coordinates) {
if (coordinates == null || coordinates.size() == 0) {
throw new IllegalArgumentException("cannot create point collection with empty set of points");
}
this.coordinates = coordinates;
}
/** ctor from serialized stream input */
protected ShapeBuilder(StreamInput in) throws IOException {
int size = in.readVInt();
coordinates = new ArrayList<>(size);
for (int i=0; i < size; i++) {
coordinates.add(readFromStream(in));
}
}
protected static Coordinate readFromStream(StreamInput in) throws IOException {
return new Coordinate(in.readDouble(), in.readDouble());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(coordinates.size());
for (Coordinate point : coordinates) {
writeCoordinateTo(point, out);
}
}
protected static void writeCoordinateTo(Coordinate coordinate, StreamOutput out) throws IOException {
out.writeDouble(coordinate.x);
out.writeDouble(coordinate.y);
}
@SuppressWarnings("unchecked")
private E thisRef() {
return (E)this;
}
/**
* Add a new coordinate to the collection
* @param longitude longitude of the coordinate
* @param latitude latitude of the coordinate
* @return this
*/
public E coordinate(double longitude, double latitude) {
return this.coordinate(new Coordinate(longitude, latitude));
}
/**
* Add a new coordinate to the collection
* @param coordinate coordinate of the point
* @return this
*/
public E coordinate(Coordinate coordinate) {
this.coordinates.add(coordinate);
return thisRef();
}
/**
* Add a array of coordinates to the collection
*
* @param coordinates array of {@link Coordinate}s to add
* @return this
*/
public E coordinates(Coordinate...coordinates) {
return this.coordinates(Arrays.asList(coordinates));
}
/**
* Add a collection of coordinates to the collection
*
* @param coordinates array of {@link Coordinate}s to add
* @return this
*/
public E coordinates(Collection<? extends Coordinate> coordinates) {
this.coordinates.addAll(coordinates);
return thisRef();
}
/**
* Copy all coordinate 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 = coordinates.toArray(new Coordinate[coordinates.size() + (closed?1:0)]);
if(closed) {
result[result.length-1] = result[0];
}
return result;
}
protected JtsGeometry jtsGeometry(Geometry geom) {
@ -104,84 +200,7 @@ public abstract class ShapeBuilder implements NamedWriteable, ToXContentObject {
* 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 cases
if (token != XContentParser.Token.START_ARRAY &&
token != XContentParser.Token.END_ARRAY &&
token != XContentParser.Token.VALUE_NULL) {
double lon = parser.doubleValue();
token = parser.nextToken();
double lat = parser.doubleValue();
token = parser.nextToken();
while (token == XContentParser.Token.VALUE_NUMBER) {
token = parser.nextToken();
}
return new CoordinateNode(new Coordinate(lon, lat));
} else if (token == XContentParser.Token.VALUE_NULL) {
throw new IllegalArgumentException("coordinates cannot contain NULL values)");
}
List<CoordinateNode> nodes = new ArrayList<>();
while (token != XContentParser.Token.END_ARRAY) {
nodes.add(parseCoordinates(parser));
token = parser.nextToken();
}
return new CoordinateNode(nodes);
}
/**
* Create a new {@link ShapeBuilder} from {@link XContent}
* @param parser parser to read the GeoShape from
* @return {@link ShapeBuilder} read from the parser or null
* if the parsers current token has been <code>null</code>
* @throws IOException if the input could not be read
*/
public static ShapeBuilder parse(XContentParser parser) throws IOException {
return GeoShapeType.parse(parser, null);
}
/**
* Create a new {@link ShapeBuilder} from {@link XContent}
* @param parser parser to read the GeoShape from
* @param geoDocMapper document field mapper reference required for spatial parameters relevant
* to the shape construction process (e.g., orientation)
* todo: refactor to place build specific parameters in the SpatialContext
* @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, GeoShapeFieldMapper geoDocMapper) throws IOException {
return GeoShapeType.parse(parser, geoDocMapper);
}
protected static XContentBuilder toXContent(XContentBuilder builder, Coordinate coordinate) throws IOException {
return builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
}
protected static void writeCoordinateTo(Coordinate coordinate, StreamOutput out) throws IOException {
out.writeDouble(coordinate.x);
out.writeDouble(coordinate.y);
}
protected static Coordinate readFromStream(StreamInput in) throws IOException {
return new Coordinate(in.readDouble(), in.readDouble());
}
public abstract T build();
protected static Coordinate shift(Coordinate coordinate, double dateline) {
if (dateline == 0) {
@ -255,58 +274,6 @@ public abstract class ShapeBuilder implements NamedWriteable, ToXContentObject {
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 ToXContentObject {
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;
}
protected boolean isEmpty() {
return (coordinate == null && (children == null || children.isEmpty()));
}
@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
@ -415,293 +382,50 @@ public abstract class ShapeBuilder implements NamedWriteable, ToXContentObject {
}
}
public static final String FIELD_TYPE = "type";
public static final String FIELD_COORDINATES = "coordinates";
public static final String FIELD_GEOMETRIES = "geometries";
public static final String FIELD_ORIENTATION = "orientation";
protected static final boolean debugEnabled() {
return LOGGER.isDebugEnabled() || DEBUG;
}
protected static XContentBuilder toXContent(XContentBuilder builder, Coordinate coordinate) throws IOException {
return builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
}
/**
* Enumeration that lists all {@link GeoShapeType}s that can be handled
* 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
*/
public enum GeoShapeType {
POINT("point"),
MULTIPOINT("multipoint"),
LINESTRING("linestring"),
MULTILINESTRING("multilinestring"),
POLYGON("polygon"),
MULTIPOLYGON("multipolygon"),
GEOMETRYCOLLECTION("geometrycollection"),
ENVELOPE("envelope"),
CIRCLE("circle");
private final String shapename;
GeoShapeType(String shapename) {
this.shapename = shapename;
protected XContentBuilder coordinatesToXcontent(XContentBuilder builder, boolean closed) throws IOException {
builder.startArray();
for(Coordinate coord : coordinates) {
toXContent(builder, coord);
}
protected String shapeName() {
return 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 IllegalArgumentException("unknown geo_shape ["+geoshapename+"]");
}
public static ShapeBuilder parse(XContentParser parser) throws IOException {
return parse(parser, null);
}
/**
* Parse the geometry specified by the source document and return a ShapeBuilder instance used to
* build the actual geometry
* @param parser - parse utility object including source document
* @param shapeMapper - field mapper needed for index specific parameters
* @return ShapeBuilder - a builder instance used to create the geometry
*/
public static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper shapeMapper) 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;
GeometryCollectionBuilder geometryCollections = null;
Orientation requestedOrientation = (shapeMapper == null) ? Orientation.RIGHT : shapeMapper.fieldType().orientation();
boolean coerce = (shapeMapper == null) ? GeoShapeFieldMapper.Defaults.COERCE.value() : shapeMapper.coerce().value();
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 (FIELD_GEOMETRIES.equals(fieldName)) {
parser.nextToken();
geometryCollections = parseGeometries(parser, shapeMapper);
} else if (CircleBuilder.FIELD_RADIUS.equals(fieldName)) {
parser.nextToken();
radius = Distance.parseDistance(parser.text());
} else if (FIELD_ORIENTATION.equals(fieldName)) {
parser.nextToken();
requestedOrientation = Orientation.fromString(parser.text());
} else {
parser.nextToken();
parser.skipChildren();
}
}
}
if (shapeType == null) {
throw new ElasticsearchParseException("shape type not included");
} else if (node == null && GeoShapeType.GEOMETRYCOLLECTION != shapeType) {
throw new ElasticsearchParseException("coordinates not included");
} else if (geometryCollections == null && GeoShapeType.GEOMETRYCOLLECTION == shapeType) {
throw new ElasticsearchParseException("geometries not included");
} else if (radius != null && GeoShapeType.CIRCLE != shapeType) {
throw new ElasticsearchParseException("field [{}] is supported for [{}] only", CircleBuilder.FIELD_RADIUS,
CircleBuilder.TYPE);
}
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, requestedOrientation, coerce);
case MULTIPOLYGON: return parseMultiPolygon(node, requestedOrientation, coerce);
case CIRCLE: return parseCircle(node, radius);
case ENVELOPE: return parseEnvelope(node);
case GEOMETRYCOLLECTION: return geometryCollections;
default:
throw new ElasticsearchParseException("shape type [{}] not included", shapeType);
if(closed) {
Coordinate start = coordinates.get(0);
Coordinate end = coordinates.get(coordinates.size()-1);
if(start.x != end.x || start.y != end.y) {
toXContent(builder, coordinates.get(0));
}
}
builder.endArray();
return builder;
}
protected static void validatePointNode(CoordinateNode node) {
if (node.isEmpty()) {
throw new ElasticsearchParseException(
"invalid number of points (0) provided when expecting a single coordinate ([lat, lng])");
} else if (node.coordinate == null) {
if (node.children.isEmpty() == false) {
throw new ElasticsearchParseException("multipoint data provided when single point data expected.");
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ShapeBuilder)) return false;
protected static PointBuilder parsePoint(CoordinateNode node) {
validatePointNode(node);
return ShapeBuilders.newPoint(node.coordinate);
}
ShapeBuilder<?,?> that = (ShapeBuilder<?,?>) o;
protected static CircleBuilder parseCircle(CoordinateNode coordinates, Distance radius) {
return ShapeBuilders.newCircleBuilder().center(coordinates.coordinate).radius(radius);
}
return Objects.equals(coordinates, that.coordinates);
}
protected static EnvelopeBuilder parseEnvelope(CoordinateNode coordinates) {
// validate the coordinate array for envelope type
if (coordinates.children.size() != 2) {
throw new ElasticsearchParseException(
"invalid number of points [{}] provided for geo_shape [{}] when expecting an array of 2 coordinates",
coordinates.children.size(), GeoShapeType.ENVELOPE.shapename);
}
// verify coordinate bounds, correct if necessary
Coordinate uL = coordinates.children.get(0).coordinate;
Coordinate lR = coordinates.children.get(1).coordinate;
if (((lR.x < uL.x) || (uL.y < lR.y))) {
Coordinate uLtmp = uL;
uL = new Coordinate(Math.min(uL.x, lR.x), Math.max(uL.y, lR.y));
lR = new Coordinate(Math.max(uLtmp.x, lR.x), Math.min(uLtmp.y, lR.y));
}
return ShapeBuilders.newEnvelope(uL, lR);
}
protected static void validateMultiPointNode(CoordinateNode coordinates) {
if (coordinates.children == null || coordinates.children.isEmpty()) {
if (coordinates.coordinate != null) {
throw new ElasticsearchParseException("single coordinate found when expecting an array of " +
"coordinates. change type to point or change data to an array of >0 coordinates");
}
throw new ElasticsearchParseException("no data provided for multipoint object when expecting " +
">0 points (e.g., [[lat, lng]] or [[lat, lng], ...])");
} else {
for (CoordinateNode point : coordinates.children) {
validatePointNode(point);
}
}
}
protected static MultiPointBuilder parseMultiPoint(CoordinateNode coordinates) {
validateMultiPointNode(coordinates);
CoordinatesBuilder points = new CoordinatesBuilder();
for (CoordinateNode node : coordinates.children) {
points.coordinate(node.coordinate);
}
return new MultiPointBuilder(points.build());
}
protected static LineStringBuilder parseLineString(CoordinateNode coordinates) {
/**
* Per GeoJSON spec (http://geojson.org/geojson-spec.html#linestring)
* "coordinates" member must be an array of two or more positions
* LineStringBuilder should throw a graceful exception if < 2 coordinates/points are provided
*/
if (coordinates.children.size() < 2) {
throw new ElasticsearchParseException("invalid number of points in LineString (found [{}] - must be >= 2)",
coordinates.children.size());
}
CoordinatesBuilder line = new CoordinatesBuilder();
for (CoordinateNode node : coordinates.children) {
line.coordinate(node.coordinate);
}
return ShapeBuilders.newLineString(line);
}
protected static MultiLineStringBuilder parseMultiLine(CoordinateNode coordinates) {
MultiLineStringBuilder multiline = ShapeBuilders.newMultiLinestring();
for (CoordinateNode node : coordinates.children) {
multiline.linestring(parseLineString(node));
}
return multiline;
}
protected static LineStringBuilder parseLinearRing(CoordinateNode coordinates, boolean coerce) {
/**
* Per GeoJSON spec (http://geojson.org/geojson-spec.html#linestring)
* A LinearRing is closed LineString with 4 or more positions. The first and last positions
* are equivalent (they represent equivalent points). Though a LinearRing is not explicitly
* represented as a GeoJSON geometry type, it is referred to in the Polygon geometry type definition.
*/
if (coordinates.children == null) {
String error = "Invalid LinearRing found.";
error += (coordinates.coordinate == null) ?
" No coordinate array provided" : " Found a single coordinate when expecting a coordinate array";
throw new ElasticsearchParseException(error);
}
int numValidPts = coerce ? 3 : 4;
if (coordinates.children.size() < numValidPts) {
throw new ElasticsearchParseException("invalid number of points in LinearRing (found [{}] - must be >= [{}])",
coordinates.children.size(), numValidPts);
}
if (!coordinates.children.get(0).coordinate.equals(
coordinates.children.get(coordinates.children.size() - 1).coordinate)) {
if (coerce) {
coordinates.children.add(coordinates.children.get(0));
} else {
throw new ElasticsearchParseException("invalid LinearRing found (coordinates are not closed)");
}
}
return parseLineString(coordinates);
}
protected static PolygonBuilder parsePolygon(CoordinateNode coordinates, final Orientation orientation, final boolean coerce) {
if (coordinates.children == null || coordinates.children.isEmpty()) {
throw new ElasticsearchParseException(
"invalid LinearRing provided for type polygon. Linear ring must be an array of coordinates");
}
LineStringBuilder shell = parseLinearRing(coordinates.children.get(0), coerce);
PolygonBuilder polygon = new PolygonBuilder(shell, orientation);
for (int i = 1; i < coordinates.children.size(); i++) {
polygon.hole(parseLinearRing(coordinates.children.get(i), coerce));
}
return polygon;
}
protected static MultiPolygonBuilder parseMultiPolygon(CoordinateNode coordinates, final Orientation orientation,
final boolean coerce) {
MultiPolygonBuilder polygons = ShapeBuilders.newMultiPolygon(orientation);
for (CoordinateNode node : coordinates.children) {
polygons.polygon(parsePolygon(node, orientation, coerce));
}
return polygons;
}
/**
* Parse the geometries array of a GeometryCollection
*
* @param parser Parser that will be read from
* @return Geometry[] geometries of the GeometryCollection
* @throws IOException Thrown if an error occurs while reading from the XContentParser
*/
protected static GeometryCollectionBuilder parseGeometries(XContentParser parser, GeoShapeFieldMapper mapper) throws
IOException {
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
throw new ElasticsearchParseException("geometries must be an array of geojson objects");
}
XContentParser.Token token = parser.nextToken();
GeometryCollectionBuilder geometryCollection = ShapeBuilders.newGeometryCollection();
while (token != XContentParser.Token.END_ARRAY) {
ShapeBuilder shapeBuilder = GeoShapeType.parse(parser);
geometryCollection.shape(shapeBuilder);
token = parser.nextToken();
}
return geometryCollection;
}
@Override
public int hashCode() {
return Objects.hash(coordinates);
}
@Override

View File

@ -1,153 +0,0 @@
/*
* Licensed to Elasticsearch 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.util.List;
import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
/**
* A collection of static methods for creating ShapeBuilders.
*/
public class ShapeBuilders {
/**
* 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 ShapeBuilders.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(List<Coordinate> points) {
return new MultiPointBuilder(points);
}
/**
* Create a new lineString
* @return a new {@link LineStringBuilder}
*/
public static LineStringBuilder newLineString(List<Coordinate> list) {
return new LineStringBuilder(list);
}
/**
* Create a new lineString
* @return a new {@link LineStringBuilder}
*/
public static LineStringBuilder newLineString(CoordinatesBuilder coordinates) {
return new LineStringBuilder(coordinates);
}
/**
* Create a new Collection of lineStrings
* @return a new {@link MultiLineStringBuilder}
*/
public static MultiLineStringBuilder newMultiLinestring() {
return new MultiLineStringBuilder();
}
/**
* Create a new PolygonBuilder
* @return a new {@link PolygonBuilder}
*/
public static PolygonBuilder newPolygon(List<Coordinate> shell) {
return new PolygonBuilder(new CoordinatesBuilder().coordinates(shell));
}
/**
* Create a new PolygonBuilder
* @return a new {@link PolygonBuilder}
*/
public static PolygonBuilder newPolygon(CoordinatesBuilder shell) {
return new PolygonBuilder(shell);
}
/**
* Create a new Collection of polygons
* @return a new {@link MultiPolygonBuilder}
*/
public static MultiPolygonBuilder newMultiPolygon() {
return new MultiPolygonBuilder();
}
/**
* Create a new Collection of polygons
* @return a new {@link MultiPolygonBuilder}
*/
public static MultiPolygonBuilder newMultiPolygon(ShapeBuilder.Orientation orientation) {
return new MultiPolygonBuilder(orientation);
}
/**
* Create a new GeometryCollection
* @return a new {@link GeometryCollectionBuilder}
*/
public static GeometryCollectionBuilder newGeometryCollection() {
return new GeometryCollectionBuilder();
}
/**
* 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(Coordinate topLeft, Coordinate bottomRight) {
return new EnvelopeBuilder(topLeft, bottomRight);
}
public static void register(List<Entry> namedWriteables) {
namedWriteables.add(new Entry(ShapeBuilder.class, PointBuilder.TYPE.shapeName(), PointBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, CircleBuilder.TYPE.shapeName(), CircleBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, EnvelopeBuilder.TYPE.shapeName(), EnvelopeBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, MultiPointBuilder.TYPE.shapeName(), MultiPointBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, LineStringBuilder.TYPE.shapeName(), LineStringBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, MultiLineStringBuilder.TYPE.shapeName(), MultiLineStringBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, PolygonBuilder.TYPE.shapeName(), PolygonBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, MultiPolygonBuilder.TYPE.shapeName(), MultiPolygonBuilder::new));
namedWriteables.add(new Entry(ShapeBuilder.class, GeometryCollectionBuilder.TYPE.shapeName(), GeometryCollectionBuilder::new));
}
}

View File

@ -0,0 +1,81 @@
/*
* Licensed to Elasticsearch 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.parsers;
import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.List;
/**
* Node used to represent a tree of coordinates.
* <p>
* Can either be a leaf node consisting of a Coordinate, or a parent with
* children
*/
public class CoordinateNode implements ToXContentObject {
public final Coordinate coordinate;
public 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;
}
public boolean isEmpty() {
return (coordinate == null && (children == null || children.isEmpty()));
}
public boolean isMultiPoint() {
return children != null && children.size() > 1;
}
@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;
}
}

View File

@ -0,0 +1,194 @@
/*
* Licensed to Elasticsearch 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.parsers;
import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.builders.CircleBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Parses shape geometry represented in geojson
*
* complies with geojson specification: https://tools.ietf.org/html/rfc7946
*/
abstract class GeoJsonParser {
protected static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper shapeMapper)
throws IOException {
GeoShapeType shapeType = null;
DistanceUnit.Distance radius = null;
CoordinateNode coordinateNode = null;
GeometryCollectionBuilder geometryCollections = null;
ShapeBuilder.Orientation requestedOrientation =
(shapeMapper == null) ? ShapeBuilder.Orientation.RIGHT : shapeMapper.fieldType().orientation();
Explicit<Boolean> coerce = (shapeMapper == null) ? GeoShapeFieldMapper.Defaults.COERCE : shapeMapper.coerce();
String malformedException = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
String fieldName = parser.currentName();
if (ShapeParser.FIELD_TYPE.match(fieldName)) {
parser.nextToken();
final GeoShapeType type = GeoShapeType.forName(parser.text());
if (shapeType != null && shapeType.equals(type) == false) {
malformedException = ShapeParser.FIELD_TYPE + " already parsed as ["
+ shapeType + "] cannot redefine as [" + type + "]";
} else {
shapeType = type;
}
} else if (ShapeParser.FIELD_COORDINATES.match(fieldName)) {
parser.nextToken();
coordinateNode = parseCoordinates(parser);
} else if (ShapeParser.FIELD_GEOMETRIES.match(fieldName)) {
if (shapeType == null) {
shapeType = GeoShapeType.GEOMETRYCOLLECTION;
} else if (shapeType.equals(GeoShapeType.GEOMETRYCOLLECTION) == false) {
malformedException = "cannot have [" + ShapeParser.FIELD_GEOMETRIES + "] with type set to ["
+ shapeType + "]";
}
parser.nextToken();
geometryCollections = parseGeometries(parser, shapeMapper);
} else if (CircleBuilder.FIELD_RADIUS.match(fieldName)) {
if (shapeType == null) {
shapeType = GeoShapeType.CIRCLE;
} else if (shapeType != null && shapeType.equals(GeoShapeType.CIRCLE) == false) {
malformedException = "cannot have [" + CircleBuilder.FIELD_RADIUS + "] with type set to ["
+ shapeType + "]";
}
parser.nextToken();
radius = DistanceUnit.Distance.parseDistance(parser.text());
} else if (ShapeParser.FIELD_ORIENTATION.match(fieldName)) {
if (shapeType != null
&& (shapeType.equals(GeoShapeType.POLYGON) || shapeType.equals(GeoShapeType.MULTIPOLYGON)) == false) {
malformedException = "cannot have [" + ShapeParser.FIELD_ORIENTATION + "] with type set to [" + shapeType + "]";
}
parser.nextToken();
requestedOrientation = ShapeBuilder.Orientation.fromString(parser.text());
} else {
parser.nextToken();
parser.skipChildren();
}
}
}
if (malformedException != null) {
throw new ElasticsearchParseException(malformedException);
} else if (shapeType == null) {
throw new ElasticsearchParseException("shape type not included");
} else if (coordinateNode == null && GeoShapeType.GEOMETRYCOLLECTION != shapeType) {
throw new ElasticsearchParseException("coordinates not included");
} else if (geometryCollections == null && GeoShapeType.GEOMETRYCOLLECTION == shapeType) {
throw new ElasticsearchParseException("geometries not included");
} else if (radius != null && GeoShapeType.CIRCLE != shapeType) {
throw new ElasticsearchParseException("field [{}] is supported for [{}] only", CircleBuilder.FIELD_RADIUS,
CircleBuilder.TYPE);
}
if (shapeType == null) {
throw new ElasticsearchParseException("shape type [{}] not included", shapeType);
}
if (shapeType.equals(GeoShapeType.GEOMETRYCOLLECTION)) {
return geometryCollections;
}
return shapeType.getBuilder(coordinateNode, radius, requestedOrientation, coerce.value());
}
/**
* 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 cases
if (token != XContentParser.Token.START_ARRAY &&
token != XContentParser.Token.END_ARRAY &&
token != XContentParser.Token.VALUE_NULL) {
return new CoordinateNode(parseCoordinate(parser));
} else if (token == XContentParser.Token.VALUE_NULL) {
throw new IllegalArgumentException("coordinates cannot contain NULL values)");
}
List<CoordinateNode> nodes = new ArrayList<>();
while (token != XContentParser.Token.END_ARRAY) {
nodes.add(parseCoordinates(parser));
token = parser.nextToken();
}
return new CoordinateNode(nodes);
}
private static Coordinate parseCoordinate(XContentParser parser) throws IOException {
double lon = parser.doubleValue();
parser.nextToken();
double lat = parser.doubleValue();
XContentParser.Token token = parser.nextToken();
while (token == XContentParser.Token.VALUE_NUMBER) {
token = parser.nextToken();
}
// todo support z/alt
return new Coordinate(lon, lat);
}
/**
* Parse the geometries array of a GeometryCollection
*
* @param parser Parser that will be read from
* @return Geometry[] geometries of the GeometryCollection
* @throws IOException Thrown if an error occurs while reading from the XContentParser
*/
static GeometryCollectionBuilder parseGeometries(XContentParser parser, GeoShapeFieldMapper mapper) throws
IOException {
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
throw new ElasticsearchParseException("geometries must be an array of geojson objects");
}
XContentParser.Token token = parser.nextToken();
GeometryCollectionBuilder geometryCollection = new GeometryCollectionBuilder();
while (token != XContentParser.Token.END_ARRAY) {
ShapeBuilder shapeBuilder = ShapeParser.parse(parser);
geometryCollection.shape(shapeBuilder);
token = parser.nextToken();
}
return geometryCollection;
}
}

View File

@ -0,0 +1,68 @@
/*
* Licensed to Elasticsearch 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.parsers;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import java.io.IOException;
/**
* first point of entry for a shape parser
*/
public interface ShapeParser {
ParseField FIELD_TYPE = new ParseField("type");
ParseField FIELD_COORDINATES = new ParseField("coordinates");
ParseField FIELD_GEOMETRIES = new ParseField("geometries");
ParseField FIELD_ORIENTATION = new ParseField("orientation");
/**
* Create a new {@link ShapeBuilder} from {@link XContent}
* @param parser parser to read the GeoShape from
* @param shapeMapper document field mapper reference required for spatial parameters relevant
* to the shape construction process (e.g., orientation)
* todo: refactor to place build specific parameters in the SpatialContext
* @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
*/
static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper shapeMapper) throws IOException {
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return null;
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
return GeoJsonParser.parse(parser, shapeMapper);
}
throw new ElasticsearchParseException("shape must be an object consisting of type and coordinates");
}
/**
* 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
*/
static ShapeBuilder parse(XContentParser parser) throws IOException {
return parse(parser, null);
}
}

View File

@ -36,6 +36,7 @@ import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -468,7 +469,7 @@ public class GeoShapeFieldMapper extends FieldMapper {
try {
Shape shape = context.parseExternalValue(Shape.class);
if (shape == null) {
ShapeBuilder shapeBuilder = ShapeBuilder.parse(context.parser(), this);
ShapeBuilder shapeBuilder = ShapeParser.parse(context.parser(), this);
if (shapeBuilder == null) {
return null;
}
@ -476,7 +477,7 @@ public class GeoShapeFieldMapper extends FieldMapper {
}
if (fieldType().pointsOnly() && !(shape instanceof Point)) {
throw new MapperParsingException("[{" + fieldType().name() + "}] is configured for points only but a " +
((shape instanceof JtsGeometry) ? ((JtsGeometry)shape).getGeom().getGeometryType() : shape.getClass()) + " was found");
((shape instanceof JtsGeometry) ? ((JtsGeometry) shape).getGeom().getGeometryType() : shape.getClass()) + " was found");
}
List<IndexableField> fields = new ArrayList<>(Arrays.asList(fieldType().defaultStrategy().createIndexableFields(shape)));
createFieldNamesField(context, fields);

View File

@ -39,6 +39,7 @@ import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.ShapesAvailability;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@ -410,7 +411,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
if (pathElements[currentPathSlot].equals(parser.currentName())) {
parser.nextToken();
if (++currentPathSlot == pathElements.length) {
listener.onResponse(ShapeBuilder.parse(parser));
listener.onResponse(ShapeParser.parse(parser));
}
} else {
parser.nextToken();
@ -517,7 +518,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
currentFieldName = parser.currentName();
token = parser.nextToken();
if (SHAPE_FIELD.match(currentFieldName)) {
shape = ShapeBuilder.parse(parser);
shape = ShapeParser.parse(parser);
} else if (STRATEGY_FIELD.match(currentFieldName)) {
String strategyName = parser.text();
strategy = SpatialStrategy.fromString(strategyName);

View File

@ -21,8 +21,8 @@ package org.elasticsearch.search;
import org.apache.lucene.search.BooleanQuery;
import org.elasticsearch.common.NamedRegistry;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.ShapesAvailability;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
import org.elasticsearch.common.io.stream.Writeable;
@ -250,6 +250,7 @@ import org.elasticsearch.search.suggest.phrase.StupidBackoff;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@ -531,7 +532,7 @@ public class SearchModule {
private void registerShapes() {
if (ShapesAvailability.JTS_AVAILABLE && ShapesAvailability.SPATIAL4J_AVAILABLE) {
ShapeBuilders.register(namedWriteables);
namedWriteables.addAll(GeoShapeType.getShapeWriteables());
}
}

View File

@ -0,0 +1,40 @@
/*
* Licensed to Elasticsearch 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.vividsolutions.jts.geom.GeometryFactory;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT;
/**
* Created by nknize on 9/22/17.
*/
abstract class BaseGeoParsingTestCase extends ESTestCase {
protected static final GeometryFactory GEOMETRY_FACTORY = SPATIAL_CONTEXT.getGeometryFactory();
public abstract void testParsePoint() throws IOException;
public abstract void testParseMultiPoint() throws IOException;
public abstract void testParseLineString() throws IOException;
public abstract void testParseMultiLineString() throws IOException;
public abstract void testParsePolygon() throws IOException;
public abstract void testParseMultiPolygon() throws IOException;
}

View File

@ -21,7 +21,6 @@ package org.elasticsearch.common.geo;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
@ -29,12 +28,11 @@ import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Circle;
@ -55,11 +53,10 @@ import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT
/**
* Tests for {@code GeoJSONShapeParser}
*/
public class GeoJSONShapeParserTests extends ESTestCase {
public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
private static final GeometryFactory GEOMETRY_FACTORY = SPATIAL_CONTEXT.getGeometryFactory();
public void testParseSimplePoint() throws IOException {
@Override
public void testParsePoint() throws IOException {
XContentBuilder pointGeoJson = XContentFactory.jsonBuilder()
.startObject()
.field("type", "Point")
@ -70,6 +67,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
}
@Override
public void testParseLineString() throws IOException {
XContentBuilder lineGeoJson = XContentFactory.jsonBuilder()
.startObject()
@ -89,6 +87,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
assertGeometryEquals(jtsGeom(expected), lineGeoJson);
}
@Override
public void testParseMultiLineString() throws IOException {
XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder()
.startObject()
@ -205,7 +204,8 @@ public class GeoJSONShapeParserTests extends ESTestCase {
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
}
public void testParsePolygonNoHoles() throws IOException {
@Override
public void testParsePolygon() throws IOException {
XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder()
.startObject()
.field("type", "Polygon")
@ -344,7 +344,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
parser.nextToken();
Shape shape = ShapeBuilder.parse(parser).build();
Shape shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
@ -364,7 +364,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
@ -384,7 +384,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
@ -404,7 +404,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
}
@ -432,7 +432,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
parser.nextToken();
Shape shape = ShapeBuilder.parse(parser).build();
Shape shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
@ -458,7 +458,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
@ -484,7 +484,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
@ -510,7 +510,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
}
@ -671,6 +671,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class);
}
@Override
public void testParseMultiPoint() throws IOException {
XContentBuilder multiPointGeoJson = XContentFactory.jsonBuilder()
.startObject()
@ -687,6 +688,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
assertGeometryEquals(expected, multiPointGeoJson);
}
@Override
public void testParseMultiPolygon() throws IOException {
// test #1: two polygons; one without hole, one with hole
XContentBuilder multiPolygonGeoJson = XContentFactory.jsonBuilder()
@ -882,7 +884,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
XContentParser parser = createParser(polygonGeoJson);
parser.nextToken();
Shape shape = ShapeBuilder.parse(parser).build();
Shape shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
@ -911,7 +913,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
@ -940,7 +942,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
@ -969,7 +971,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
@ -998,7 +1000,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
@ -1027,7 +1029,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
parser = createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
shape = ShapeParser.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
}
@ -1035,7 +1037,7 @@ public class GeoJSONShapeParserTests extends ESTestCase {
private void assertGeometryEquals(Shape expected, XContentBuilder geoJson) throws IOException {
XContentParser parser = createParser(geoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertEquals(expected, ShapeBuilder.parse(parser).build());
ElasticsearchGeoAssertions.assertEquals(expected, ShapeParser.parse(parser).build());
}
private ShapeCollection<Shape> shapeCollection(Shape... shapes) {

View File

@ -24,10 +24,13 @@ import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Polygon;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.CircleBuilder;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.LineStringBuilder;
import org.elasticsearch.common.geo.builders.MultiLineStringBuilder;
import org.elasticsearch.common.geo.builders.PointBuilder;
import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.test.ESTestCase;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Circle;
@ -46,13 +49,13 @@ import static org.hamcrest.Matchers.containsString;
public class ShapeBuilderTests extends ESTestCase {
public void testNewPoint() {
Point point = ShapeBuilders.newPoint(-100, 45).build();
Point point = new PointBuilder().coordinate(-100, 45).build();
assertEquals(-100D, point.getX(), 0.0d);
assertEquals(45D, point.getY(), 0.0d);
}
public void testNewRectangle() {
Rectangle rectangle = ShapeBuilders.newEnvelope(new Coordinate(-45, 30), new Coordinate(45, -30)).build();
Rectangle rectangle = new EnvelopeBuilder(new Coordinate(-45, 30), new Coordinate(45, -30)).build();
assertEquals(-45D, rectangle.getMinX(), 0.0d);
assertEquals(-30D, rectangle.getMinY(), 0.0d);
assertEquals(45D, rectangle.getMaxX(), 0.0d);
@ -60,7 +63,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testNewPolygon() {
Polygon polygon = ShapeBuilders.newPolygon(new CoordinatesBuilder()
Polygon polygon = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-45, 30)
.coordinate(45, 30)
.coordinate(45, -30)
@ -75,7 +78,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testNewPolygon_coordinate() {
Polygon polygon = ShapeBuilders.newPolygon(new CoordinatesBuilder()
Polygon polygon = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(new Coordinate(-45, 30))
.coordinate(new Coordinate(45, 30))
.coordinate(new Coordinate(45, -30))
@ -90,7 +93,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testNewPolygon_coordinates() {
Polygon polygon = ShapeBuilders.newPolygon(new CoordinatesBuilder()
Polygon polygon = new PolygonBuilder(new CoordinatesBuilder()
.coordinates(new Coordinate(-45, 30), new Coordinate(45, 30), new Coordinate(45, -30), new Coordinate(-45, -30), new Coordinate(-45, 30))
).toPolygon();
@ -103,7 +106,7 @@ public class ShapeBuilderTests extends ESTestCase {
public void testLineStringBuilder() {
// Building a simple LineString
ShapeBuilders.newLineString(new CoordinatesBuilder()
new LineStringBuilder(new CoordinatesBuilder()
.coordinate(-130.0, 55.0)
.coordinate(-130.0, -40.0)
.coordinate(-15.0, -40.0)
@ -114,7 +117,7 @@ public class ShapeBuilderTests extends ESTestCase {
.coordinate(-110.0, 55.0)).build();
// Building a linestring that needs to be wrapped
ShapeBuilders.newLineString(new CoordinatesBuilder()
new LineStringBuilder(new CoordinatesBuilder()
.coordinate(100.0, 50.0)
.coordinate(110.0, -40.0)
.coordinate(240.0, -40.0)
@ -127,7 +130,7 @@ public class ShapeBuilderTests extends ESTestCase {
.build();
// Building a lineString on the dateline
ShapeBuilders.newLineString(new CoordinatesBuilder()
new LineStringBuilder(new CoordinatesBuilder()
.coordinate(-180.0, 80.0)
.coordinate(-180.0, 40.0)
.coordinate(-180.0, -40.0)
@ -136,7 +139,7 @@ public class ShapeBuilderTests extends ESTestCase {
.build();
// Building a lineString on the dateline
ShapeBuilders.newLineString(new CoordinatesBuilder()
new LineStringBuilder(new CoordinatesBuilder()
.coordinate(180.0, 80.0)
.coordinate(180.0, 40.0)
.coordinate(180.0, -40.0)
@ -146,7 +149,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testMultiLineString() {
ShapeBuilders.newMultiLinestring()
new MultiLineStringBuilder()
.linestring(new LineStringBuilder(new CoordinatesBuilder()
.coordinate(-100.0, 50.0)
.coordinate(50.0, 50.0)
@ -164,7 +167,7 @@ public class ShapeBuilderTests extends ESTestCase {
.build();
// LineString that needs to be wrapped
ShapeBuilders.newMultiLinestring()
new MultiLineStringBuilder()
.linestring(new LineStringBuilder(new CoordinatesBuilder()
.coordinate(150.0, 60.0)
.coordinate(200.0, 60.0)
@ -183,7 +186,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testPolygonSelfIntersection() {
PolygonBuilder newPolygon = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder newPolygon = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-40.0, 50.0)
.coordinate(40.0, 50.0)
.coordinate(-40.0, -50.0)
@ -194,31 +197,31 @@ public class ShapeBuilderTests extends ESTestCase {
public void testGeoCircle() {
double earthCircumference = 40075016.69;
Circle circle = ShapeBuilders.newCircleBuilder().center(0, 0).radius("100m").build();
Circle circle = new CircleBuilder().center(0, 0).radius("100m").build();
assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001);
assertEquals(new PointImpl(0, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter());
circle = ShapeBuilders.newCircleBuilder().center(+180, 0).radius("100m").build();
circle = new CircleBuilder().center(+180, 0).radius("100m").build();
assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001);
assertEquals(new PointImpl(180, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter());
circle = ShapeBuilders.newCircleBuilder().center(-180, 0).radius("100m").build();
circle = new CircleBuilder().center(-180, 0).radius("100m").build();
assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001);
assertEquals(new PointImpl(-180, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter());
circle = ShapeBuilders.newCircleBuilder().center(0, 90).radius("100m").build();
circle = new CircleBuilder().center(0, 90).radius("100m").build();
assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001);
assertEquals(new PointImpl(0, 90, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter());
circle = ShapeBuilders.newCircleBuilder().center(0, -90).radius("100m").build();
circle = new CircleBuilder().center(0, -90).radius("100m").build();
assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001);
assertEquals(new PointImpl(0, -90, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter());
double randomLat = (randomDouble() * 180) - 90;
double randomLon = (randomDouble() * 360) - 180;
double randomRadius = randomIntBetween(1, (int) earthCircumference / 4);
circle = ShapeBuilders.newCircleBuilder().center(randomLon, randomLat).radius(randomRadius + "m").build();
circle = new CircleBuilder().center(randomLon, randomLat).radius(randomRadius + "m").build();
assertEquals((360 * randomRadius) / earthCircumference, circle.getRadius(), 0.00000001);
assertEquals(new PointImpl(randomLon, randomLat, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter());
}
public void testPolygonWrapping() {
Shape shape = ShapeBuilders.newPolygon(new CoordinatesBuilder()
Shape shape = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-150.0, 65.0)
.coordinate(-250.0, 65.0)
.coordinate(-250.0, -65.0)
@ -231,7 +234,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testLineStringWrapping() {
Shape shape = ShapeBuilders.newLineString(new CoordinatesBuilder()
Shape shape = new LineStringBuilder(new CoordinatesBuilder()
.coordinate(-150.0, 65.0)
.coordinate(-250.0, 65.0)
.coordinate(-250.0, -65.0)
@ -248,7 +251,7 @@ public class ShapeBuilderTests extends ESTestCase {
// expected results: 3 polygons, 1 with a hole
// a giant c shape
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(174,0)
.coordinate(-176,0)
.coordinate(-176,3)
@ -292,7 +295,7 @@ public class ShapeBuilderTests extends ESTestCase {
// expected results: 3 polygons, 1 with a hole
// a giant c shape
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-186,0)
.coordinate(-176,0)
.coordinate(-176,3)
@ -331,7 +334,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testComplexShapeWithHole() {
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-85.0018514,37.1311314)
.coordinate(-85.0016645,37.1315293)
.coordinate(-85.0016246,37.1317069)
@ -407,7 +410,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testShapeWithHoleAtEdgeEndPoints() {
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-4, 2)
.coordinate(4, 2)
.coordinate(6, 0)
@ -430,7 +433,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testShapeWithPointOnDateline() {
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(180, 0)
.coordinate(176, 4)
.coordinate(176, -4)
@ -443,7 +446,7 @@ public class ShapeBuilderTests extends ESTestCase {
public void testShapeWithEdgeAlongDateline() {
// test case 1: test the positive side of the dateline
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(180, 0)
.coordinate(176, 4)
.coordinate(180, -4)
@ -454,7 +457,7 @@ public class ShapeBuilderTests extends ESTestCase {
assertPolygon(shape);
// test case 2: test the negative side of the dateline
builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-176, 4)
.coordinate(-180, 0)
.coordinate(-180, -4)
@ -467,7 +470,7 @@ public class ShapeBuilderTests extends ESTestCase {
public void testShapeWithBoundaryHoles() {
// test case 1: test the positive side of the dateline
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-177, 10)
.coordinate(176, 15)
.coordinate(172, 0)
@ -486,7 +489,7 @@ public class ShapeBuilderTests extends ESTestCase {
assertMultiPolygon(shape);
// test case 2: test the negative side of the dateline
builder = ShapeBuilders.newPolygon(
builder = new PolygonBuilder(
new CoordinatesBuilder()
.coordinate(-176, 15)
.coordinate(179, 10)
@ -510,7 +513,7 @@ public class ShapeBuilderTests extends ESTestCase {
public void testShapeWithTangentialHole() {
// test a shape with one tangential (shared) vertex (should pass)
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(179, 10)
.coordinate(168, 15)
.coordinate(164, 0)
@ -531,7 +534,7 @@ public class ShapeBuilderTests extends ESTestCase {
public void testShapeWithInvalidTangentialHole() {
// test a shape with one invalid tangential (shared) vertex (should throw exception)
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(179, 10)
.coordinate(168, 15)
.coordinate(164, 0)
@ -552,7 +555,7 @@ public class ShapeBuilderTests extends ESTestCase {
public void testBoundaryShapeWithTangentialHole() {
// test a shape with one tangential (shared) vertex for each hole (should pass)
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-177, 10)
.coordinate(176, 15)
.coordinate(172, 0)
@ -579,7 +582,7 @@ public class ShapeBuilderTests extends ESTestCase {
public void testBoundaryShapeWithInvalidTangentialHole() {
// test shape with two tangential (shared) vertices (should throw exception)
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-177, 10)
.coordinate(176, 15)
.coordinate(172, 0)
@ -602,7 +605,7 @@ public class ShapeBuilderTests extends ESTestCase {
* Test an enveloping polygon around the max mercator bounds
*/
public void testBoundaryShape() {
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-180, 90)
.coordinate(180, 90)
.coordinate(180, -90)
@ -616,7 +619,7 @@ public class ShapeBuilderTests extends ESTestCase {
public void testShapeWithAlternateOrientation() {
// cw: should produce a multi polygon spanning hemispheres
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(180, 0)
.coordinate(176, 4)
.coordinate(-176, 4)
@ -627,7 +630,7 @@ public class ShapeBuilderTests extends ESTestCase {
assertPolygon(shape);
// cw: geo core will convert to ccw across the dateline
builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(180, 0)
.coordinate(-176, 4)
.coordinate(176, 4)
@ -640,7 +643,7 @@ public class ShapeBuilderTests extends ESTestCase {
}
public void testInvalidShapeWithConsecutiveDuplicatePoints() {
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(180, 0)
.coordinate(176, 4)
.coordinate(176, 4)

View File

@ -19,13 +19,14 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
@ -33,8 +34,6 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
@ -49,9 +48,7 @@ public abstract class AbstractShapeBuilderTestCase<SB extends ShapeBuilder> exte
@BeforeClass
public static void init() {
if (namedWriteableRegistry == null) {
List<NamedWriteableRegistry.Entry> shapes = new ArrayList<>();
ShapeBuilders.register(shapes);
namedWriteableRegistry = new NamedWriteableRegistry(shapes);
namedWriteableRegistry = new NamedWriteableRegistry(GeoShapeType.getShapeWriteables());
}
}
@ -82,9 +79,9 @@ public abstract class AbstractShapeBuilderTestCase<SB extends ShapeBuilder> exte
}
XContentBuilder builder = testShape.toXContent(contentBuilder, ToXContent.EMPTY_PARAMS);
XContentBuilder shuffled = shuffleXContent(builder);
XContentParser shapeParser = createParser(shuffled);
shapeParser.nextToken();
ShapeBuilder parsedShape = ShapeBuilder.parse(shapeParser);
XContentParser shapeContentParser = createParser(shuffled);
shapeContentParser.nextToken();
ShapeBuilder parsedShape = ShapeParser.parse(shapeContentParser);
assertNotSame(testShape, parsedShape);
assertEquals(testShape, parsedShape);
assertEquals(testShape.hashCode(), parsedShape.hashCode());

View File

@ -49,7 +49,7 @@ public class LineStringBuilderTests extends AbstractShapeBuilderTestCase<LineStr
}
static LineStringBuilder mutate(LineStringBuilder original) throws IOException {
LineStringBuilder mutation = (LineStringBuilder) copyShape(original);
LineStringBuilder mutation = copyShape(original);
Coordinate[] coordinates = original.coordinates(false);
Coordinate coordinate = randomFrom(coordinates);
if (randomBoolean()) {
@ -65,7 +65,7 @@ public class LineStringBuilderTests extends AbstractShapeBuilderTestCase<LineStr
coordinate.y = randomDoubleBetween(-90.0, 90.0, true);
}
}
return mutation.coordinates(coordinates);
return LineStringBuilder.class.cast(mutation.coordinates(coordinates));
}
static LineStringBuilder createRandomShape() {

View File

@ -68,6 +68,6 @@ public class MultiLineStringBuilderTests extends AbstractShapeBuilderTestCase<Mu
}
static MultiLineStringBuilder createRandomShape() {
return new MultiLineStringBuilder();
return MultiLineStringBuilder.class.cast(RandomShapeGenerator.createShape(random(), ShapeType.MULTILINESTRING));
}
}

View File

@ -70,7 +70,7 @@ public class MultiPointBuilderTests extends AbstractShapeBuilderTestCase<MultiPo
} else {
coordinates = new Coordinate[]{new Coordinate(1.0, 1.0)};
}
return mutation.coordinates(coordinates);
return MultiPointBuilder.class.cast(mutation.coordinates(coordinates));
}
static MultiPointBuilder createRandomShape() {

View File

@ -24,13 +24,13 @@ import org.apache.lucene.index.Term;
import org.apache.lucene.search.DocValuesFieldExistsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.elasticsearch.common.geo.builders.PointBuilder;
import org.locationtech.spatial4j.shape.Point;
import org.elasticsearch.common.collect.Iterators;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.QueryShardContext;
import org.locationtech.spatial4j.shape.Point;
import java.io.IOException;
import java.nio.charset.Charset;
@ -181,7 +181,7 @@ public class ExternalMapper extends FieldMapper {
pointMapper.parse(context.createExternalValueContext(point));
// Let's add a Dummy Shape
Point shape = ShapeBuilders.newPoint(-100, 45).build();
Point shape = new PointBuilder(-100, 45).build();
shapeMapper.parse(context.createExternalValueContext(shape));
context = context.createExternalValueContext(generatedValue);

View File

@ -21,7 +21,7 @@ package org.elasticsearch.index.mapper;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.common.geo.builders.PointBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.plugins.Plugin;
@ -118,7 +118,7 @@ public class ExternalValuesMapperIntegrationIT extends ESIntegTestCase {
assertThat(response.getHits().getTotalHits(), equalTo((long) 1));
response = client().prepareSearch("test-idx")
.setPostFilter(QueryBuilders.geoShapeQuery("field.shape", ShapeBuilders.newPoint(-100, 45)).relation(ShapeRelation.WITHIN))
.setPostFilter(QueryBuilders.geoShapeQuery("field.shape", new PointBuilder(-100, 45)).relation(ShapeRelation.WITHIN))
.execute().actionGet();
assertThat(response.getHits().getTotalHits(), equalTo((long) 1));

View File

@ -32,7 +32,6 @@ import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
@ -200,7 +199,7 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
// see #3878
public void testThatXContentSerializationInsideOfArrayWorks() throws Exception {
EnvelopeBuilder envelopeBuilder = ShapeBuilders.newEnvelope(new Coordinate(0, 0), new Coordinate(10, 10));
EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(0, 0), new Coordinate(10, 10));
GeoShapeQueryBuilder geoQuery = QueryBuilders.geoShapeQuery("searchGeometry", envelopeBuilder);
JsonXContent.contentBuilder().startArray().value(geoQuery).endArray();
}

View File

@ -40,8 +40,8 @@ import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.LineStringBuilder;
import org.elasticsearch.common.geo.builders.MultiPolygonBuilder;
import org.elasticsearch.common.geo.builders.PointBuilder;
import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.Settings;
@ -121,7 +121,7 @@ public class GeoFilterIT extends ESIntegTestCase {
public void testShapeBuilders() {
try {
// self intersection polygon
ShapeBuilders.newPolygon(new CoordinatesBuilder()
new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-10, -10)
.coordinate(10, 10)
.coordinate(-10, 10)
@ -133,13 +133,13 @@ public class GeoFilterIT extends ESIntegTestCase {
}
// polygon with hole
ShapeBuilders.newPolygon(new CoordinatesBuilder()
new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-10, -10).coordinate(-10, 10).coordinate(10, 10).coordinate(10, -10).close())
.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(-5, -5).coordinate(-5, 5).coordinate(5, 5).coordinate(5, -5).close()))
.build();
try {
// polygon with overlapping hole
ShapeBuilders.newPolygon(new CoordinatesBuilder()
new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-10, -10).coordinate(-10, 10).coordinate(10, 10).coordinate(10, -10).close())
.hole(new LineStringBuilder(new CoordinatesBuilder()
.coordinate(-5, -5).coordinate(-5, 11).coordinate(5, 11).coordinate(5, -5).close()))
@ -151,7 +151,7 @@ public class GeoFilterIT extends ESIntegTestCase {
try {
// polygon with intersection holes
ShapeBuilders.newPolygon(new CoordinatesBuilder()
new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-10, -10).coordinate(-10, 10).coordinate(10, 10).coordinate(10, -10).close())
.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(-5, -5).coordinate(-5, 5).coordinate(5, 5).coordinate(5, -5).close()))
.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(-5, -6).coordinate(5, -6).coordinate(5, -4).coordinate(-5, -4).close()))
@ -162,7 +162,7 @@ public class GeoFilterIT extends ESIntegTestCase {
try {
// Common line in polygon
ShapeBuilders.newPolygon(new CoordinatesBuilder()
new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-10, -10)
.coordinate(-10, 10)
.coordinate(-5, 10)
@ -177,8 +177,7 @@ public class GeoFilterIT extends ESIntegTestCase {
}
// Multipolygon: polygon with hole and polygon within the whole
ShapeBuilders
.newMultiPolygon()
new MultiPolygonBuilder()
.polygon(new PolygonBuilder(
new CoordinatesBuilder().coordinate(-10, -10)
.coordinate(-10, 10)
@ -223,7 +222,7 @@ public class GeoFilterIT extends ESIntegTestCase {
// Create a multipolygon with two polygons. The first is an rectangle of size 10x10
// with a hole of size 5x5 equidistant from all sides. This hole in turn contains
// the second polygon of size 4x4 equidistant from all sites
MultiPolygonBuilder polygon = ShapeBuilders.newMultiPolygon()
MultiPolygonBuilder polygon = new MultiPolygonBuilder()
.polygon(new PolygonBuilder(
new CoordinatesBuilder().coordinate(-10, -10).coordinate(-10, 10).coordinate(10, 10).coordinate(10, -10).close())
.hole(new LineStringBuilder(new CoordinatesBuilder()
@ -238,7 +237,7 @@ public class GeoFilterIT extends ESIntegTestCase {
// Point in polygon
SearchResponse result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(3, 3)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(3, 3)))
.execute().actionGet();
assertHitCount(result, 1);
assertFirstHit(result, hasId("1"));
@ -246,7 +245,7 @@ public class GeoFilterIT extends ESIntegTestCase {
// Point in polygon hole
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(4.5, 4.5)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(4.5, 4.5)))
.execute().actionGet();
assertHitCount(result, 0);
@ -257,7 +256,7 @@ public class GeoFilterIT extends ESIntegTestCase {
// Point on polygon border
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(10.0, 5.0)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(10.0, 5.0)))
.execute().actionGet();
assertHitCount(result, 1);
assertFirstHit(result, hasId("1"));
@ -265,7 +264,7 @@ public class GeoFilterIT extends ESIntegTestCase {
// Point on hole border
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(5.0, 2.0)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(5.0, 2.0)))
.execute().actionGet();
assertHitCount(result, 1);
assertFirstHit(result, hasId("1"));
@ -274,21 +273,21 @@ public class GeoFilterIT extends ESIntegTestCase {
// Point not in polygon
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoDisjointQuery("area", ShapeBuilders.newPoint(3, 3)))
.setPostFilter(QueryBuilders.geoDisjointQuery("area", new PointBuilder(3, 3)))
.execute().actionGet();
assertHitCount(result, 0);
// Point in polygon hole
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoDisjointQuery("area", ShapeBuilders.newPoint(4.5, 4.5)))
.setPostFilter(QueryBuilders.geoDisjointQuery("area", new PointBuilder(4.5, 4.5)))
.execute().actionGet();
assertHitCount(result, 1);
assertFirstHit(result, hasId("1"));
}
// Create a polygon that fills the empty area of the polygon defined above
PolygonBuilder inverse = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder inverse = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-5, -5).coordinate(-5, 5).coordinate(5, 5).coordinate(5, -5).close())
.hole(new LineStringBuilder(
new CoordinatesBuilder().coordinate(-4, -4).coordinate(-4, 4).coordinate(4, 4).coordinate(4, -4).close()));
@ -300,20 +299,20 @@ public class GeoFilterIT extends ESIntegTestCase {
// re-check point on polygon hole
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(4.5, 4.5)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(4.5, 4.5)))
.execute().actionGet();
assertHitCount(result, 1);
assertFirstHit(result, hasId("2"));
// Create Polygon with hole and common edge
PolygonBuilder builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-10, -10).coordinate(-10, 10).coordinate(10, 10).coordinate(10, -10).close())
.hole(new LineStringBuilder(new CoordinatesBuilder()
.coordinate(-5, -5).coordinate(-5, 5).coordinate(10, 5).coordinate(10, -5).close()));
if (withinSupport) {
// Polygon WithIn Polygon
builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(-30, -30).coordinate(-30, 30).coordinate(30, 30).coordinate(30, -30).close());
result = client().prepareSearch()
@ -324,7 +323,7 @@ public class GeoFilterIT extends ESIntegTestCase {
}
// Create a polygon crossing longitude 180.
builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close());
data = jsonBuilder().startObject().field("area", builder).endObject().bytes();
@ -332,7 +331,7 @@ public class GeoFilterIT extends ESIntegTestCase {
client().admin().indices().prepareRefresh().execute().actionGet();
// Create a polygon crossing longitude 180 with hole.
builder = ShapeBuilders.newPolygon(new CoordinatesBuilder()
builder = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close())
.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(175, -5).coordinate(185, -5).coordinate(185, 5).coordinate(175, 5).close()));
@ -342,25 +341,25 @@ public class GeoFilterIT extends ESIntegTestCase {
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(174, -4)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(174, -4)))
.execute().actionGet();
assertHitCount(result, 1);
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(-174, -4)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(-174, -4)))
.execute().actionGet();
assertHitCount(result, 1);
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(180, -4)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(180, -4)))
.execute().actionGet();
assertHitCount(result, 0);
result = client().prepareSearch()
.setQuery(matchAllQuery())
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", ShapeBuilders.newPoint(180, -6)))
.setPostFilter(QueryBuilders.geoIntersectionQuery("area", new PointBuilder(180, -6)))
.execute().actionGet();
assertHitCount(result, 1);
}

View File

@ -19,7 +19,12 @@
package org.elasticsearch.search.geo;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
import org.elasticsearch.common.geo.builders.LineStringBuilder;
import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.locationtech.spatial4j.shape.Rectangle;
@ -28,12 +33,6 @@ import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
import org.elasticsearch.common.geo.builders.LineStringBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.mapper.MapperParsingException;
@ -102,7 +101,7 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
.endObject()
.endObject()).setRefreshPolicy(IMMEDIATE).get();
ShapeBuilder shape = ShapeBuilders.newEnvelope(new Coordinate(-45, 45), new Coordinate(45, -45));
EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45));
SearchResponse searchResponse = client().prepareSearch("test").setTypes("type1")
.setQuery(geoIntersectionQuery("location", shape))
@ -146,7 +145,7 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
.endObject()
.endObject()).setRefreshPolicy(IMMEDIATE).get();
ShapeBuilder query = ShapeBuilders.newEnvelope(new Coordinate(-122.88, 48.62), new Coordinate(-122.82, 48.54));
EnvelopeBuilder query = new EnvelopeBuilder(new Coordinate(-122.88, 48.62), new Coordinate(-122.82, 48.54));
// This search would fail if both geoshape indexing and geoshape filtering
// used the bottom-level optimization in SpatialPrefixTree#recursiveGetNodes.
@ -171,7 +170,7 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
createIndex("shapes");
ensureGreen();
ShapeBuilder shape = ShapeBuilders.newEnvelope(new Coordinate(-45, 45), new Coordinate(45, -45));
EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45));
client().prepareIndex("shapes", "shape_type", "Big_Rectangle").setSource(jsonBuilder().startObject()
.field("shape", shape).endObject()).setRefreshPolicy(IMMEDIATE).get();
@ -215,7 +214,7 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
createIndex("shapes", Settings.EMPTY, "shape_type", "_source", "enabled=false");
ensureGreen();
ShapeBuilder shape = ShapeBuilders.newEnvelope(new Coordinate(-45, 45), new Coordinate(45, -45));
EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45));
client().prepareIndex("shapes", "shape_type", "Big_Rectangle").setSource(jsonBuilder().startObject()
.field("shape", shape).endObject()).setRefreshPolicy(IMMEDIATE).get();
@ -226,12 +225,12 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
}
public void testReusableBuilder() throws IOException {
ShapeBuilder polygon = ShapeBuilders.newPolygon(new CoordinatesBuilder()
PolygonBuilder polygon = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close())
.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(175, -5).coordinate(185, -5).coordinate(185, 5).coordinate(175, 5).close()));
assertUnmodified(polygon);
ShapeBuilder linestring = ShapeBuilders.newLineString(new CoordinatesBuilder()
LineStringBuilder linestring = new LineStringBuilder(new CoordinatesBuilder()
.coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close());
assertUnmodified(linestring);
}
@ -403,9 +402,9 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery(
"location",
ShapeBuilders.newGeometryCollection()
new GeometryCollectionBuilder()
.polygon(
ShapeBuilders.newPolygon(new CoordinatesBuilder().coordinate(99.0, -1.0).coordinate(99.0, 3.0).coordinate(103.0, 3.0).coordinate(103.0, -1.0)
new PolygonBuilder(new CoordinatesBuilder().coordinate(99.0, -1.0).coordinate(99.0, 3.0).coordinate(103.0, 3.0).coordinate(103.0, -1.0)
.coordinate(99.0, -1.0)))).relation(ShapeRelation.INTERSECTS);
SearchResponse result = client().prepareSearch("test").setTypes("type").setQuery(QueryBuilders.matchAllQuery())
.setPostFilter(filter).get();
@ -413,24 +412,24 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
assertHitCount(result, 1);
filter = QueryBuilders.geoShapeQuery(
"location",
ShapeBuilders.newGeometryCollection().polygon(
ShapeBuilders.newPolygon(new CoordinatesBuilder().coordinate(199.0, -11.0).coordinate(199.0, 13.0).coordinate(193.0, 13.0).coordinate(193.0, -11.0)
new GeometryCollectionBuilder().polygon(
new PolygonBuilder(new CoordinatesBuilder().coordinate(199.0, -11.0).coordinate(199.0, 13.0).coordinate(193.0, 13.0).coordinate(193.0, -11.0)
.coordinate(199.0, -11.0)))).relation(ShapeRelation.INTERSECTS);
result = client().prepareSearch("test").setTypes("type").setQuery(QueryBuilders.matchAllQuery())
.setPostFilter(filter).get();
assertSearchResponse(result);
assertHitCount(result, 0);
filter = QueryBuilders.geoShapeQuery("location", ShapeBuilders.newGeometryCollection()
.polygon(ShapeBuilders.newPolygon(new CoordinatesBuilder().coordinate(99.0, -1.0).coordinate(99.0, 3.0).coordinate(103.0, 3.0).coordinate(103.0, -1.0).coordinate(99.0, -1.0)))
filter = QueryBuilders.geoShapeQuery("location", new GeometryCollectionBuilder()
.polygon(new PolygonBuilder(new CoordinatesBuilder().coordinate(99.0, -1.0).coordinate(99.0, 3.0).coordinate(103.0, 3.0).coordinate(103.0, -1.0).coordinate(99.0, -1.0)))
.polygon(
ShapeBuilders.newPolygon(new CoordinatesBuilder().coordinate(199.0, -11.0).coordinate(199.0, 13.0).coordinate(193.0, 13.0).coordinate(193.0, -11.0)
new PolygonBuilder(new CoordinatesBuilder().coordinate(199.0, -11.0).coordinate(199.0, 13.0).coordinate(193.0, 13.0).coordinate(193.0, -11.0)
.coordinate(199.0, -11.0)))).relation(ShapeRelation.INTERSECTS);
result = client().prepareSearch("test").setTypes("type").setQuery(QueryBuilders.matchAllQuery())
.setPostFilter(filter).get();
assertSearchResponse(result);
assertHitCount(result, 1);
// no shape
filter = QueryBuilders.geoShapeQuery("location", ShapeBuilders.newGeometryCollection());
filter = QueryBuilders.geoShapeQuery("location", new GeometryCollectionBuilder());
result = client().prepareSearch("test").setTypes("type").setQuery(QueryBuilders.matchAllQuery())
.setPostFilter(filter).get();
assertSearchResponse(result);

View File

@ -24,7 +24,6 @@ import com.vividsolutions.jts.algorithm.ConvexHull;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.geo.builders.CoordinateCollection;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
import org.elasticsearch.common.geo.builders.LineStringBuilder;
@ -193,7 +192,7 @@ public class RandomShapeGenerator extends RandomGeoGenerator {
p = xRandomPointIn(r, within);
coordinatesBuilder.coordinate(p.getX(), p.getY());
}
CoordinateCollection pcb = (st == ShapeType.MULTIPOINT) ? new MultiPointBuilder(coordinatesBuilder.build()) : new LineStringBuilder(coordinatesBuilder);
ShapeBuilder pcb = (st == ShapeType.MULTIPOINT) ? new MultiPointBuilder(coordinatesBuilder.build()) : new LineStringBuilder(coordinatesBuilder);
return pcb;
case MULTILINESTRING:
MultiLineStringBuilder mlsb = new MultiLineStringBuilder();

View File

@ -19,6 +19,7 @@
package org.elasticsearch.test.hamcrest;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;
import org.locationtech.spatial4j.shape.impl.GeoCircle;
@ -34,7 +35,6 @@ import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentParser;
import org.hamcrest.Matcher;
@ -256,7 +256,7 @@ public class ElasticsearchGeoAssertions {
public static void assertValidException(XContentParser parser, Class expectedException) {
try {
ShapeBuilder.parse(parser).build();
ShapeParser.parse(parser).build();
Assert.fail("process completed successfully when " + expectedException.getName() + " expected");
} catch (Exception e) {
assert(e.getClass().equals(expectedException)):