[Geo] Add Well Known Text (WKT) Parsing Support to ShapeBuilders

This commit adds WKT support to Geo ShapeBuilders.

This supports the following format:

POINT (30 10)
LINESTRING (30 10, 10 30, 40 40)
BBOX (-10, 10, 10, -10)
POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))
POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))
MULTIPOINT ((10 40), (40 30), (20 20), (30 10))
MULTIPOINT (10 40, 40 30, 20 20, 30 10)
MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))
MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))
MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))
GEOMETRYCOLLECTION (POINT (30 10), MULTIPOINT ((10 40), (40 30), (20 20), (30 10)))

closes #9120
This commit is contained in:
Nicholas Knize 2017-11-16 13:35:30 -06:00
parent 5b03e3b53d
commit 8bcf5393f2
14 changed files with 933 additions and 56 deletions

View File

@ -241,6 +241,11 @@ public enum GeoShapeType {
}
return coordinates;
}
@Override
public String wktName() {
return BBOX;
}
},
CIRCLE("circle") {
@Override
@ -273,11 +278,13 @@ public enum GeoShapeType {
private final String shapename;
private static Map<String, GeoShapeType> shapeTypeMap = new HashMap<>();
private static final String BBOX = "BBOX";
static {
for (GeoShapeType type : values()) {
shapeTypeMap.put(type.shapename, type);
}
shapeTypeMap.put(ENVELOPE.wktName().toLowerCase(Locale.ROOT), ENVELOPE);
}
GeoShapeType(String shapename) {
@ -300,6 +307,11 @@ public enum GeoShapeType {
ShapeBuilder.Orientation orientation, boolean coerce);
abstract CoordinateNode validate(CoordinateNode coordinates, boolean coerce);
/** wkt shape name */
public String wktName() {
return this.shapename;
}
public static List<Entry> getShapeWriteables() {
List<Entry> namedWriteables = new ArrayList<>();
namedWriteables.add(new Entry(ShapeBuilder.class, PointBuilder.TYPE.shapeName(), PointBuilder::new));
@ -313,4 +325,9 @@ public enum GeoShapeType {
namedWriteables.add(new Entry(ShapeBuilder.class, GeometryCollectionBuilder.TYPE.shapeName(), GeometryCollectionBuilder::new));
return namedWriteables;
}
@Override
public String toString() {
return this.shapename;
}
}

View File

@ -168,6 +168,11 @@ public class CircleBuilder extends ShapeBuilder<Circle, CircleBuilder> {
return TYPE;
}
@Override
public String toWKT() {
throw new UnsupportedOperationException("The WKT spec does not support CIRCLE geometry");
}
@Override
public int hashCode() {
return Objects.hash(center, radius, unit.ordinal());

View File

@ -20,6 +20,7 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.GeoWKTParser;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.locationtech.spatial4j.shape.Rectangle;
import com.vividsolutions.jts.geom.Coordinate;
@ -70,6 +71,28 @@ public class EnvelopeBuilder extends ShapeBuilder<Rectangle, EnvelopeBuilder> {
return this.bottomRight;
}
@Override
protected StringBuilder contentToWKT() {
StringBuilder sb = new StringBuilder();
sb.append(GeoWKTParser.LPAREN);
// minX, maxX, maxY, minY
sb.append(topLeft.x);
sb.append(GeoWKTParser.COMMA);
sb.append(GeoWKTParser.SPACE);
sb.append(bottomRight.x);
sb.append(GeoWKTParser.COMMA);
sb.append(GeoWKTParser.SPACE);
// TODO support Z??
sb.append(topLeft.y);
sb.append(GeoWKTParser.COMMA);
sb.append(GeoWKTParser.SPACE);
sb.append(bottomRight.y);
sb.append(GeoWKTParser.RPAREN);
return sb;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -21,6 +21,7 @@ package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.geo.parsers.GeoWKTParser;
import org.locationtech.spatial4j.shape.Shape;
import org.elasticsearch.ElasticsearchException;
@ -136,6 +137,23 @@ public class GeometryCollectionBuilder extends ShapeBuilder {
return builder.endObject();
}
@Override
protected StringBuilder contentToWKT() {
StringBuilder sb = new StringBuilder();
if (shapes.isEmpty()) {
sb.append(GeoWKTParser.EMPTY);
} else {
sb.append(GeoWKTParser.LPAREN);
sb.append(shapes.get(0).toWKT());
for (int i = 1; i < shapes.size(); ++i) {
sb.append(GeoWKTParser.COMMA);
sb.append(shapes.get(i).toWKT());
}
sb.append(GeoWKTParser.RPAREN);
}
return sb;
}
@Override
public GeoShapeType type() {
return TYPE;

View File

@ -20,8 +20,8 @@
package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.GeoWKTParser;
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;
import com.vividsolutions.jts.geom.LineString;
@ -82,6 +82,25 @@ public class MultiLineStringBuilder extends ShapeBuilder<JtsGeometry, MultiLineS
return TYPE;
}
@Override
protected StringBuilder contentToWKT() {
final StringBuilder sb = new StringBuilder();
if (lines.isEmpty()) {
sb.append(GeoWKTParser.EMPTY);
} else {
sb.append(GeoWKTParser.LPAREN);
if (lines.size() > 0) {
sb.append(ShapeBuilder.coordinateListToWKT(lines.get(0).coordinates));
}
for (int i = 1; i < lines.size(); ++i) {
sb.append(GeoWKTParser.COMMA);
sb.append(ShapeBuilder.coordinateListToWKT(lines.get(i).coordinates));
}
sb.append(GeoWKTParser.RPAREN);
}
return sb;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -21,6 +21,7 @@ package org.elasticsearch.common.geo.builders;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.geo.parsers.GeoWKTParser;
import org.locationtech.spatial4j.shape.Shape;
import com.vividsolutions.jts.geom.Coordinate;
@ -101,6 +102,37 @@ public class MultiPolygonBuilder extends ShapeBuilder {
return polygons;
}
private static String polygonCoordinatesToWKT(PolygonBuilder polygon) {
StringBuilder sb = new StringBuilder();
sb.append(GeoWKTParser.LPAREN);
sb.append(ShapeBuilder.coordinateListToWKT(polygon.shell().coordinates));
for (LineStringBuilder hole : polygon.holes()) {
sb.append(GeoWKTParser.COMMA);
sb.append(ShapeBuilder.coordinateListToWKT(hole.coordinates));
}
sb.append(GeoWKTParser.RPAREN);
return sb.toString();
}
@Override
protected StringBuilder contentToWKT() {
final StringBuilder sb = new StringBuilder();
if (polygons.isEmpty()) {
sb.append(GeoWKTParser.EMPTY);
} else {
sb.append(GeoWKTParser.LPAREN);
if (polygons.size() > 0) {
sb.append(polygonCoordinatesToWKT(polygons.get(0)));
}
for (int i = 1; i < polygons.size(); ++i) {
sb.append(GeoWKTParser.COMMA);
sb.append(polygonCoordinatesToWKT(polygons.get(i)));
}
sb.append(GeoWKTParser.RPAREN);
}
return sb;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -729,6 +729,19 @@ public class PolygonBuilder extends ShapeBuilder<JtsGeometry, PolygonBuilder> {
}
}
@Override
protected StringBuilder contentToWKT() {
StringBuilder sb = new StringBuilder();
sb.append('(');
sb.append(ShapeBuilder.coordinateListToWKT(shell.coordinates));
for (LineStringBuilder hole : holes) {
sb.append(", ");
sb.append(ShapeBuilder.coordinateListToWKT(hole.coordinates));
}
sb.append(')');
return sb;
}
@Override
public int hashCode() {
return Objects.hash(shell, holes, orientation);

View File

@ -27,6 +27,7 @@ import org.apache.logging.log4j.Logger;
import org.elasticsearch.Assertions;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.GeoWKTParser;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
@ -339,6 +340,47 @@ public abstract class ShapeBuilder<T extends Shape, E extends ShapeBuilder<T,E>>
}
}
protected StringBuilder contentToWKT() {
return coordinateListToWKT(this.coordinates);
}
public String toWKT() {
StringBuilder sb = new StringBuilder();
sb.append(type().wktName());
sb.append(GeoWKTParser.SPACE);
sb.append(contentToWKT());
return sb.toString();
}
protected static StringBuilder coordinateListToWKT(final List<Coordinate> coordinates) {
final StringBuilder sb = new StringBuilder();
if (coordinates.isEmpty()) {
sb.append(GeoWKTParser.EMPTY);
} else {
// walk through coordinates:
sb.append(GeoWKTParser.LPAREN);
sb.append(coordinateToWKT(coordinates.get(0)));
for (int i = 1; i < coordinates.size(); ++i) {
sb.append(GeoWKTParser.COMMA);
sb.append(GeoWKTParser.SPACE);
sb.append(coordinateToWKT(coordinates.get(i)));
}
sb.append(GeoWKTParser.RPAREN);
}
return sb;
}
private static String coordinateToWKT(final Coordinate coordinate) {
final StringBuilder sb = new StringBuilder();
sb.append(coordinate.x + GeoWKTParser.SPACE + coordinate.y);
if (Double.isNaN(coordinate.z) == false) {
sb.append(GeoWKTParser.SPACE + coordinate.z);
}
return sb.toString();
}
protected static final IntersectionOrder INTERSECTION_ORDER = new IntersectionOrder();
private static final class IntersectionOrder implements Comparator<Edge> {

View File

@ -0,0 +1,321 @@
/*
* 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.geo.GeoShapeType;
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.io.FastStringReader;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.util.List;
/**
* Parses shape geometry represented in WKT format
*
* complies with OGC® document: 12-063r5 and ISO/IEC 13249-3:2016 standard
* located at http://docs.opengeospatial.org/is/12-063r5/12-063r5.html
*/
public class GeoWKTParser {
public static final String EMPTY = "EMPTY";
public static final String SPACE = Loggers.SPACE;
public static final String LPAREN = "(";
public static final String RPAREN = ")";
public static final String COMMA = ",";
private static final String NAN = "NaN";
private static final String NUMBER = "<NUMBER>";
private static final String EOF = "END-OF-STREAM";
private static final String EOL = "END-OF-LINE";
// no instance
private GeoWKTParser() {}
public static ShapeBuilder parse(XContentParser parser)
throws IOException, ElasticsearchParseException {
FastStringReader reader = new FastStringReader(parser.text());
try {
// setup the tokenizer; configured to read words w/o numbers
StreamTokenizer tokenizer = new StreamTokenizer(reader);
tokenizer.resetSyntax();
tokenizer.wordChars('a', 'z');
tokenizer.wordChars('A', 'Z');
tokenizer.wordChars(128 + 32, 255);
tokenizer.wordChars('0', '9');
tokenizer.wordChars('-', '-');
tokenizer.wordChars('+', '+');
tokenizer.wordChars('.', '.');
tokenizer.whitespaceChars(0, ' ');
tokenizer.commentChar('#');
ShapeBuilder builder = parseGeometry(tokenizer);
checkEOF(tokenizer);
return builder;
} finally {
reader.close();
}
}
/** parse geometry from the stream tokenizer */
private static ShapeBuilder parseGeometry(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
final GeoShapeType type = GeoShapeType.forName(nextWord(stream));
switch (type) {
case POINT:
return parsePoint(stream);
case MULTIPOINT:
return parseMultiPoint(stream);
case LINESTRING:
return parseLine(stream);
case MULTILINESTRING:
return parseMultiLine(stream);
case POLYGON:
return parsePolygon(stream);
case MULTIPOLYGON:
return parseMultiPolygon(stream);
case ENVELOPE:
return parseBBox(stream);
case GEOMETRYCOLLECTION:
return parseGeometryCollection(stream);
default:
throw new IllegalArgumentException("Unknown geometry type: " + type);
}
}
private static EnvelopeBuilder parseBBox(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
double minLon = nextNumber(stream);
nextComma(stream);
double maxLon = nextNumber(stream);
nextComma(stream);
double maxLat = nextNumber(stream);
nextComma(stream);
double minLat = nextNumber(stream);
nextCloser(stream);
return new EnvelopeBuilder(new Coordinate(minLon, maxLat), new Coordinate(maxLon, minLat));
}
private static PointBuilder parsePoint(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
PointBuilder pt = new PointBuilder(nextNumber(stream), nextNumber(stream));
if (isNumberNext(stream) == true) {
nextNumber(stream);
}
nextCloser(stream);
return pt;
}
private static List<Coordinate> parseCoordinateList(StreamTokenizer stream)
throws IOException, ElasticsearchParseException {
CoordinatesBuilder coordinates = new CoordinatesBuilder();
boolean isOpenParen = false;
if (isNumberNext(stream) || (isOpenParen = nextWord(stream).equals(LPAREN))) {
coordinates.coordinate(parseCoordinate(stream));
}
if (isOpenParen && nextCloser(stream).equals(RPAREN) == false) {
throw new ElasticsearchParseException("expected: [{}]" + RPAREN + " but found: [{}]" + tokenString(stream), stream.lineno());
}
while (nextCloserOrComma(stream).equals(COMMA)) {
isOpenParen = false;
if (isNumberNext(stream) || (isOpenParen = nextWord(stream).equals(LPAREN))) {
coordinates.coordinate(parseCoordinate(stream));
}
if (isOpenParen && nextCloser(stream).equals(RPAREN) == false) {
throw new ElasticsearchParseException("expected: " + RPAREN + " but found: " + tokenString(stream), stream.lineno());
}
}
return coordinates.build();
}
private static Coordinate parseCoordinate(StreamTokenizer stream)
throws IOException, ElasticsearchParseException {
final double lon = nextNumber(stream);
final double lat = nextNumber(stream);
Double z = null;
if (isNumberNext(stream)) {
z = nextNumber(stream);
}
return z == null ? new Coordinate(lon, lat) : new Coordinate(lon, lat, z);
}
private static MultiPointBuilder parseMultiPoint(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return null;
}
return new MultiPointBuilder(parseCoordinateList(stream));
}
private static LineStringBuilder parseLine(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return null;
}
return new LineStringBuilder(parseCoordinateList(stream));
}
private static MultiLineStringBuilder parseMultiLine(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return null;
}
MultiLineStringBuilder builder = new MultiLineStringBuilder();
builder.linestring(parseLine(stream));
while (nextCloserOrComma(stream).equals(COMMA)) {
builder.linestring(parseLine(stream));
}
return builder;
}
private static PolygonBuilder parsePolygon(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
PolygonBuilder builder = new PolygonBuilder(parseLine(stream), ShapeBuilder.Orientation.RIGHT);
while (nextCloserOrComma(stream).equals(COMMA)) {
builder.hole(parseLine(stream));
}
return builder;
}
private static MultiPolygonBuilder parseMultiPolygon(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
MultiPolygonBuilder builder = new MultiPolygonBuilder().polygon(parsePolygon(stream));
while (nextCloserOrComma(stream).equals(COMMA)) {
builder.polygon(parsePolygon(stream));
}
return builder;
}
private static GeometryCollectionBuilder parseGeometryCollection(StreamTokenizer stream)
throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
GeometryCollectionBuilder builder = new GeometryCollectionBuilder().shape(parseGeometry(stream));
while (nextCloserOrComma(stream).equals(COMMA)) {
builder.shape(parseGeometry(stream));
}
return builder;
}
/** next word in the stream */
private static String nextWord(StreamTokenizer stream) throws ElasticsearchParseException, IOException {
switch (stream.nextToken()) {
case StreamTokenizer.TT_WORD:
final String word = stream.sval;
return word.equalsIgnoreCase(EMPTY) ? EMPTY : word;
case '(': return LPAREN;
case ')': return RPAREN;
case ',': return COMMA;
}
throw new ElasticsearchParseException("expected word but found: " + tokenString(stream), stream.lineno());
}
private static double nextNumber(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
if (stream.nextToken() == StreamTokenizer.TT_WORD) {
if (stream.sval.equalsIgnoreCase(NAN)) {
return Double.NaN;
} else {
try {
return Double.parseDouble(stream.sval);
} catch (NumberFormatException e) {
throw new ElasticsearchParseException("invalid number found: " + stream.sval, stream.lineno());
}
}
}
throw new ElasticsearchParseException("expected number but found: " + tokenString(stream), stream.lineno());
}
private static String tokenString(StreamTokenizer stream) {
switch (stream.ttype) {
case StreamTokenizer.TT_WORD: return stream.sval;
case StreamTokenizer.TT_EOF: return EOF;
case StreamTokenizer.TT_EOL: return EOL;
case StreamTokenizer.TT_NUMBER: return NUMBER;
}
return "'" + (char) stream.ttype + "'";
}
private static boolean isNumberNext(StreamTokenizer stream) throws IOException {
final int type = stream.nextToken();
stream.pushBack();
return type == StreamTokenizer.TT_WORD;
}
private static String nextEmptyOrOpen(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
final String next = nextWord(stream);
if (next.equals(EMPTY) || next.equals(LPAREN)) {
return next;
}
throw new ElasticsearchParseException("expected " + EMPTY + " or " + LPAREN
+ " but found: " + tokenString(stream), stream.lineno());
}
private static String nextCloser(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
if (nextWord(stream).equals(RPAREN)) {
return RPAREN;
}
throw new ElasticsearchParseException("expected " + RPAREN + " but found: " + tokenString(stream), stream.lineno());
}
private static String nextComma(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
if (nextWord(stream).equals(COMMA) == true) {
return COMMA;
}
throw new ElasticsearchParseException("expected " + COMMA + " but found: " + tokenString(stream), stream.lineno());
}
private static String nextCloserOrComma(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
String token = nextWord(stream);
if (token.equals(COMMA) || token.equals(RPAREN)) {
return token;
}
throw new ElasticsearchParseException("expected " + COMMA + " or " + RPAREN
+ " but found: " + tokenString(stream), stream.lineno());
}
/** next word in the stream */
private static void checkEOF(StreamTokenizer stream) throws ElasticsearchParseException, IOException {
if (stream.nextToken() != StreamTokenizer.TT_EOF) {
throw new ElasticsearchParseException("expected end of WKT string but found additional text: "
+ tokenString(stream), stream.lineno());
}
}
}

View File

@ -51,6 +51,8 @@ public interface ShapeParser {
return null;
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
return GeoJsonParser.parse(parser, shapeMapper);
} else if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
return GeoWKTParser.parse(parser);
}
throw new ElasticsearchParseException("shape must be an object consisting of type and coordinates");
}

View File

@ -18,10 +18,21 @@
*/
package org.elasticsearch.common.geo;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT;
@ -35,4 +46,35 @@ abstract class BaseGeoParsingTestCase extends ESTestCase {
public abstract void testParseMultiLineString() throws IOException;
public abstract void testParsePolygon() throws IOException;
public abstract void testParseMultiPolygon() throws IOException;
public abstract void testParseEnvelope() throws IOException;
public abstract void testParseGeometryCollection() throws IOException;
protected void assertValidException(XContentBuilder builder, Class expectedException) throws IOException {
XContentParser parser = createParser(builder);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, expectedException);
}
protected void assertGeometryEquals(Shape expected, XContentBuilder geoJson) throws IOException {
XContentParser parser = createParser(geoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertEquals(expected, ShapeParser.parse(parser).build());
}
protected ShapeCollection<Shape> shapeCollection(Shape... shapes) {
return new ShapeCollection<>(Arrays.asList(shapes), SPATIAL_CONTEXT);
}
protected ShapeCollection<Shape> shapeCollection(Geometry... geoms) {
List<Shape> shapes = new ArrayList<>(geoms.length);
for (Geometry geom : geoms) {
shapes.add(jtsGeom(geom));
}
return new ShapeCollection<>(shapes, SPATIAL_CONTEXT);
}
protected JtsGeometry jtsGeom(Geometry geom) {
return new JtsGeometry(geom, SPATIAL_CONTEXT, false, false);
}
}

View File

@ -20,7 +20,6 @@
package org.elasticsearch.common.geo;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
@ -39,12 +38,10 @@ import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import org.locationtech.spatial4j.shape.jts.JtsPoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT;
@ -159,6 +156,7 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
assertGeometryEquals(jtsGeom(expectedLS), lineGeoJson);
}
@Override
public void testParseEnvelope() throws IOException {
// test #1: envelope with expected coordinate order (TopLeft, BottomRight)
XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope")
@ -1033,27 +1031,4 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
}
private void assertGeometryEquals(Shape expected, XContentBuilder geoJson) throws IOException {
XContentParser parser = createParser(geoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertEquals(expected, ShapeParser.parse(parser).build());
}
private ShapeCollection<Shape> shapeCollection(Shape... shapes) {
return new ShapeCollection<>(Arrays.asList(shapes), SPATIAL_CONTEXT);
}
private ShapeCollection<Shape> shapeCollection(Geometry... geoms) {
List<Shape> shapes = new ArrayList<>(geoms.length);
for (Geometry geom : geoms) {
shapes.add(jtsGeom(geom));
}
return new ShapeCollection<>(shapes, SPATIAL_CONTEXT);
}
private JtsGeometry jtsGeom(Geometry geom) {
return new JtsGeometry(geom, SPATIAL_CONTEXT, false, false);
}
}

View File

@ -0,0 +1,255 @@
/*
* 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 com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import org.apache.lucene.geo.GeoTestUtil;
import org.elasticsearch.ElasticsearchParseException;
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.parsers.GeoWKTParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.test.geo.RandomShapeGenerator;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;
import org.locationtech.spatial4j.shape.jts.JtsPoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT;
/**
* Tests for {@code GeoWKTShapeParser}
*/
public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
private static XContentBuilder toWKTContent(ShapeBuilder builder, boolean generateMalformed)
throws IOException {
String wkt = builder.toWKT();
if (generateMalformed) {
// malformed - extra paren
// TODO generate more malformed WKT
wkt += GeoWKTParser.RPAREN;
}
if (randomBoolean()) {
// test comments
wkt = "# " + wkt + "\n" + wkt;
}
return XContentFactory.jsonBuilder().value(wkt);
}
private void assertExpected(Shape expected, ShapeBuilder builder) throws IOException {
XContentBuilder xContentBuilder = toWKTContent(builder, false);
assertGeometryEquals(expected, xContentBuilder);
}
private void assertMalformed(Shape expected, ShapeBuilder builder) throws IOException {
XContentBuilder xContentBuilder = toWKTContent(builder, true);
assertValidException(xContentBuilder, ElasticsearchParseException.class);
}
@Override
public void testParsePoint() throws IOException {
GeoPoint p = RandomShapeGenerator.randomPoint(random());
Coordinate c = new Coordinate(p.lon(), p.lat());
Point expected = GEOMETRY_FACTORY.createPoint(c);
assertExpected(new JtsPoint(expected, SPATIAL_CONTEXT), new PointBuilder().coordinate(c));
assertMalformed(new JtsPoint(expected, SPATIAL_CONTEXT), new PointBuilder().coordinate(c));
}
@Override
public void testParseMultiPoint() throws IOException {
int numPoints = randomIntBetween(2, 100);
List<Coordinate> coordinates = new ArrayList<>(numPoints);
Shape[] shapes = new Shape[numPoints];
GeoPoint p = new GeoPoint();
for (int i = 0; i < numPoints; ++i) {
p.reset(GeoTestUtil.nextLatitude(), GeoTestUtil.nextLongitude());
coordinates.add(new Coordinate(p.lon(), p.lat()));
shapes[i] = SPATIAL_CONTEXT.makePoint(p.lon(), p.lat());
}
ShapeCollection expected = shapeCollection(shapes);
assertExpected(expected, new MultiPointBuilder(coordinates));
assertMalformed(expected, new MultiPointBuilder(coordinates));
}
private List<Coordinate> randomLineStringCoords() {
int numPoints = randomIntBetween(2, 100);
List<Coordinate> coordinates = new ArrayList<>(numPoints);
GeoPoint p;
for (int i = 0; i < numPoints; ++i) {
p = RandomShapeGenerator.randomPointIn(random(), -90d, -90d, 90d, 90d);
coordinates.add(new Coordinate(p.lon(), p.lat()));
}
return coordinates;
}
@Override
public void testParseLineString() throws IOException {
List<Coordinate> coordinates = randomLineStringCoords();
LineString expected = GEOMETRY_FACTORY.createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
assertExpected(jtsGeom(expected), new LineStringBuilder(coordinates));
}
@Override
public void testParseMultiLineString() throws IOException {
int numLineStrings = randomIntBetween(2, 8);
List<LineString> lineStrings = new ArrayList<>(numLineStrings);
MultiLineStringBuilder builder = new MultiLineStringBuilder();
for (int j = 0; j < numLineStrings; ++j) {
List<Coordinate> lsc = randomLineStringCoords();
Coordinate [] coords = lsc.toArray(new Coordinate[lsc.size()]);
lineStrings.add(GEOMETRY_FACTORY.createLineString(coords));
builder.linestring(new LineStringBuilder(lsc));
}
MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(
lineStrings.toArray(new LineString[lineStrings.size()]));
assertExpected(jtsGeom(expected), builder);
assertMalformed(jtsGeom(expected), builder);
}
@Override
public void testParsePolygon() throws IOException {
PolygonBuilder builder = PolygonBuilder.class.cast(
RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.POLYGON));
Coordinate[] coords = builder.coordinates()[0][0];
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(coords);
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
assertExpected(jtsGeom(expected), builder);
assertMalformed(jtsGeom(expected), builder);
}
@Override
public void testParseMultiPolygon() throws IOException {
int numPolys = randomIntBetween(2, 8);
MultiPolygonBuilder builder = new MultiPolygonBuilder();
PolygonBuilder pb;
Coordinate[] coordinates;
Polygon[] shapes = new Polygon[numPolys];
LinearRing shell;
for (int i = 0; i < numPolys; ++i) {
pb = PolygonBuilder.class.cast(RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.POLYGON));
builder.polygon(pb);
coordinates = pb.coordinates()[0][0];
shell = GEOMETRY_FACTORY.createLinearRing(coordinates);
shapes[i] = GEOMETRY_FACTORY.createPolygon(shell, null);
}
Shape expected = shapeCollection(shapes);
assertExpected(expected, builder);
assertMalformed(expected, builder);
}
public void testParsePolygonWithHole() throws IOException {
// add 3d point to test ISSUE #10501
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 0, 15.0));
shellCoordinates.add(new Coordinate(101, 0));
shellCoordinates.add(new Coordinate(101, 1));
shellCoordinates.add(new Coordinate(100, 1, 10.0));
shellCoordinates.add(new Coordinate(100, 0));
List<Coordinate> holeCoordinates = new ArrayList<>();
holeCoordinates.add(new Coordinate(100.2, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.2));
PolygonBuilder polygonWithHole = new PolygonBuilder(new CoordinatesBuilder().coordinates(shellCoordinates));
polygonWithHole.hole(new LineStringBuilder(holeCoordinates));
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
LinearRing[] holes = new LinearRing[1];
holes[0] = GEOMETRY_FACTORY.createLinearRing(
holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes);
assertExpected(jtsGeom(expected), polygonWithHole);
assertMalformed(jtsGeom(expected), polygonWithHole);
}
public void testParseSelfCrossingPolygon() throws IOException {
// test self crossing ccw poly not crossing dateline
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(176, 15));
shellCoordinates.add(new Coordinate(-177, 10));
shellCoordinates.add(new Coordinate(-177, -10));
shellCoordinates.add(new Coordinate(176, -15));
shellCoordinates.add(new Coordinate(-177, 15));
shellCoordinates.add(new Coordinate(172, 0));
shellCoordinates.add(new Coordinate(176, 15));
PolygonBuilder poly = new PolygonBuilder(new CoordinatesBuilder().coordinates(shellCoordinates));
XContentBuilder builder = XContentFactory.jsonBuilder().value(poly.toWKT());
assertValidException(builder, InvalidShapeException.class);
}
public void testMalformedWKT() throws IOException {
// malformed points in a polygon is a common typo
String malformedWKT = "POLYGON ((100, 5) (100, 10) (90, 10), (90, 5), (100, 5)";
XContentBuilder builder = XContentFactory.jsonBuilder().value(malformedWKT);
assertValidException(builder, ElasticsearchParseException.class);
}
@Override
public void testParseEnvelope() throws IOException {
org.apache.lucene.geo.Rectangle r = GeoTestUtil.nextBox();
EnvelopeBuilder builder = new EnvelopeBuilder(new Coordinate(r.minLon, r.maxLat), new Coordinate(r.maxLon, r.minLat));
Rectangle expected = SPATIAL_CONTEXT.makeRectangle(r.minLon, r.maxLon, r.minLat, r.maxLat);
assertExpected(expected, builder);
assertMalformed(expected, builder);
}
public void testInvalidGeometryType() throws IOException {
XContentBuilder builder = XContentFactory.jsonBuilder().value("UnknownType (-1 -2)");
assertValidException(builder, IllegalArgumentException.class);
}
@Override
public void testParseGeometryCollection() throws IOException {
if (rarely()) {
// assert empty shape collection
GeometryCollectionBuilder builder = new GeometryCollectionBuilder();
Shape[] expected = new Shape[0];
assertEquals(shapeCollection(expected).isEmpty(), builder.build().isEmpty());
} else {
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
assertExpected(gcb.build(), gcb);
}
}
}

View File

@ -74,7 +74,7 @@ different ways. 1. Right-hand rule: `right`, `ccw`, `counterclockwise`,
outer ring vertices in counterclockwise order with inner ring(s) vertices (holes)
in clockwise order. Setting this parameter in the geo_shape mapping explicitly
sets vertex order for the coordinate list of a geo_shape field but can be
overridden in each individual GeoJSON document.
overridden in each individual GeoJSON or WKT document.
| `ccw`
|`points_only` |Setting this option to `true` (defaults to `false`) configures
@ -86,8 +86,9 @@ by improving point performance on a `geo_shape` field so that `geo_shape` querie
optimal on a point only field.
| `false`
|`ignore_malformed` |If true, malformed geojson shapes are ignored. If false (default),
malformed geojson shapes throw an exception and reject the whole document.
|`ignore_malformed` |If true, malformed GeoJSON or WKT shapes are ignored. If
false (default), malformed GeoJSON and WKT shapes throw an exception and reject the
entire document.
| `false`
@ -204,29 +205,30 @@ overly bloating the resulting index too much relative to the input size.
[float]
==== Input Structure
The http://www.geojson.org[GeoJSON] format is used to represent
http://geojson.org/geojson-spec.html#geometry-objects[shapes] as input
as follows:
Shapes can be represented using either the http://www.geojson.org[GeoJSON]
or http://docs.opengeospatial.org/is/12-063r5/12-063r5.html[Well-Known Text]
(WKT) format. The following table provides a mapping of GeoJSON and WKT
to Elasticsearch types:
[cols="<,<,<",options="header",]
[cols="<,<,<,<",options="header",]
|=======================================================================
|GeoJSON Type |Elasticsearch Type |Description
|GeoJSON Type |WKT Type |Elasticsearch Type |Description
|`Point` |`point` |A single geographic coordinate.
|`LineString` |`linestring` |An arbitrary line given two or more points.
|`Polygon` |`polygon` |A _closed_ polygon whose first and last point
|`Point` |`POINT` |`point` |A single geographic coordinate.
|`LineString` |`LINESTRING` |`linestring` |An arbitrary line given two or more points.
|`Polygon` |`POLYGON` |`polygon` |A _closed_ polygon whose first and last point
must match, thus requiring `n + 1` vertices to create an `n`-sided
polygon and a minimum of `4` vertices.
|`MultiPoint` |`multipoint` |An array of unconnected, but likely related
|`MultiPoint` |`MULTIPOINT` |`multipoint` |An array of unconnected, but likely related
points.
|`MultiLineString` |`multilinestring` |An array of separate linestrings.
|`MultiPolygon` |`multipolygon` |An array of separate polygons.
|`GeometryCollection` |`geometrycollection` | A GeoJSON shape similar to the
|`MultiLineString` |`MULTILINESTRING` |`multilinestring` |An array of separate linestrings.
|`MultiPolygon` |`MULTIPOLYGON` |`multipolygon` |An array of separate polygons.
|`GeometryCollection` |`GEOMETRYCOLLECTION` |`geometrycollection` | A GeoJSON shape similar to the
`multi*` shapes except that multiple types can coexist (e.g., a Point
and a LineString).
|`N/A` |`envelope` |A bounding rectangle, or envelope, specified by
|`N/A` |`BBOX` |`envelope` |A bounding rectangle, or envelope, specified by
specifying only the top left and bottom right points.
|`N/A` |`circle` |A circle specified by a center point and radius with
|`N/A` |`N/A` |`circle` |A circle specified by a center point and radius with
units, which default to `METERS`.
|=======================================================================
@ -235,7 +237,7 @@ units, which default to `METERS`.
For all types, both the inner `type` and `coordinates` fields are
required.
In GeoJSON, and therefore Elasticsearch, the correct *coordinate
In GeoJSON and WKT, and therefore Elasticsearch, the correct *coordinate
order is longitude, latitude (X, Y)* within coordinate arrays. This
differs from many Geospatial APIs (e.g., Google Maps) that generally
use the colloquial latitude, longitude (Y, X).
@ -247,7 +249,7 @@ use the colloquial latitude, longitude (Y, X).
A point is a single geographic coordinate, such as the location of a
building or the current position given by a smartphone's Geolocation
API.
API. The following is an example of a point in GeoJSON.
[source,js]
--------------------------------------------------
@ -261,12 +263,24 @@ POST /example/doc
--------------------------------------------------
// CONSOLE
The following is an example of a point in WKT:
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "POINT (-77.03653 38.897676)"
}
--------------------------------------------------
// CONSOLE
[float]
===== http://geojson.org/geojson-spec.html#id3[LineString]
A `linestring` defined by an array of two or more positions. By
specifying only two points, the `linestring` will represent a straight
line. Specifying more than two points creates an arbitrary path.
line. Specifying more than two points creates an arbitrary path. The
following is an example of a LineString in GeoJSON.
[source,js]
--------------------------------------------------
@ -280,6 +294,17 @@ POST /example/doc
--------------------------------------------------
// CONSOLE
The following is an example of a LineString in WKT:
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "LINESTRING (-77.03653 38.897676, -77.009051 38.889939)"
}
--------------------------------------------------
// CONSOLE
The above `linestring` would draw a straight line starting at the White
House to the US Capitol Building.
@ -288,7 +313,7 @@ House to the US Capitol Building.
A polygon is defined by a list of a list of points. The first and last
points in each (outer) list must be the same (the polygon must be
closed).
closed). The following is an example of a Polygon in GeoJSON.
[source,js]
--------------------------------------------------
@ -304,8 +329,20 @@ POST /example/doc
--------------------------------------------------
// CONSOLE
The following is an example of a Polygon in WKT:
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))"
}
--------------------------------------------------
// CONSOLE
The first array represents the outer boundary of the polygon, the other
arrays represent the interior shapes ("holes"):
arrays represent the interior shapes ("holes"). The following is a GeoJSON example
of a polygon with a hole:
[source,js]
--------------------------------------------------
@ -323,9 +360,21 @@ POST /example/doc
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
*IMPORTANT NOTE:* GeoJSON does not mandate a specific order for vertices thus ambiguous
polygons around the dateline and poles are possible. To alleviate ambiguity
the Open Geospatial Consortium (OGC)
The following is an example of a Polygon with a hole in WKT:
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2))"
}
--------------------------------------------------
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
*IMPORTANT NOTE:* GeoJSON and WKT do not enforce a specific order for vertices
thus ambiguous polygons around the dateline and poles are possible. To alleviate
ambiguity the Open Geospatial Consortium (OGC)
http://www.opengeospatial.org/standards/sfa[Simple Feature Access] specification
defines the following vertex ordering:
@ -380,7 +429,7 @@ POST /example/doc
[float]
===== http://www.geojson.org/geojson-spec.html#id5[MultiPoint]
A list of geojson points.
The following is an example of a list of geojson points:
[source,js]
--------------------------------------------------
@ -396,10 +445,21 @@ POST /example/doc
--------------------------------------------------
// CONSOLE
The following is an example of a list of WKT points:
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "MULTIPOINT (102.0 2.0, 103.0 2.0)"
}
--------------------------------------------------
// CONSOLE
[float]
===== http://www.geojson.org/geojson-spec.html#id6[MultiLineString]
A list of geojson linestrings.
The following is an example of a list of geojson linestrings:
[source,js]
--------------------------------------------------
@ -418,10 +478,22 @@ POST /example/doc
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
The following is an example of a list of WKT linestrings:
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "MULTILINESTRING ((102.0 2.0, 103.0 2.0, 103.0 3.0, 102.0 3.0), (100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8))"
}
--------------------------------------------------
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
[float]
===== http://www.geojson.org/geojson-spec.html#id7[MultiPolygon]
A list of geojson polygons.
The following is an example of a list of geojson polygons (second polygon contains a hole):
[source,js]
--------------------------------------------------
@ -440,10 +512,22 @@ POST /example/doc
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
The following is an example of a list of WKT polygons (second polygon contains a hole):
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "MULTIPOLYGON (((102.0 2.0, 103.0 2.0, 103.0 3.0, 102.0 3.0, 102.0 2.0)), ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))"
}
--------------------------------------------------
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
[float]
===== http://geojson.org/geojson-spec.html#geometrycollection[Geometry Collection]
A collection of geojson geometry objects.
The following is an example of a collection of geojson geometry objects:
[source,js]
--------------------------------------------------
@ -467,6 +551,19 @@ POST /example/doc
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
The following is an example of a collection of WKT geometry objects:
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "GEOMETRYCOLLECTION (POINT (100.0 0.0), LINESTRING (101.0 0.0, 102.0 1.0))"
}
--------------------------------------------------
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
[float]
===== Envelope
@ -487,6 +584,20 @@ POST /example/doc
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
The following is an example of an envelope using the WKT BBOX format:
*NOTE:* WKT specification expects the following order: minLon, maxLon, maxLat, minLat.
[source,js]
--------------------------------------------------
POST /example/doc
{
"location" : "BBOX (-45.0, 45.0, 45.0, -45.0)"
}
--------------------------------------------------
// CONSOLE
// TEST[skip:https://github.com/elastic/elasticsearch/issues/23836]
[float]
===== Circle
@ -509,6 +620,8 @@ POST /example/doc
Note: The inner `radius` field is required. If not specified, then
the units of the `radius` will default to `METERS`.
*NOTE:* Neither GeoJSON or WKT support a point-radius circle type.
[float]
==== Sorting and Retrieving index Shapes