Add Z value support to geo_shape

This enhancement adds Z value support (source only) to geo_shape fields. If vertices are provided with a third dimension, the third dimension is ignored for indexing but returned as part of source. Like beofre, any values greater than the 3rd dimension are ignored.

closes #23747
This commit is contained in:
Nicholas Knize 2017-06-19 14:29:12 -05:00
parent 794de63232
commit fede633563
29 changed files with 739 additions and 114 deletions

View File

@ -105,6 +105,13 @@ The following parameters are accepted by `geo_point` fields:
If `true`, malformed geo-points are ignored. If `false` (default),
malformed geo-points throw an exception and reject the whole document.
<<ignore_z_value,`ignore_z_value`>>::
If `true` (default) three dimension points will be accepted (stored in source)
but only latitude and longitude values will be indexed; the third dimension is
ignored. If `false`, geo-points containing any more than latitude and longitude
(two dimensions) values throw an exception and reject the whole document.
==== Using geo-points in scripts
When accessing the value of a geo-point in a script, the value is returned as

View File

@ -91,6 +91,12 @@ false (default), malformed GeoJSON and WKT shapes throw an exception and reject
entire document.
| `false`
|`ignore_z_value` |If `true` (default) three dimension points will be accepted (stored in source)
but only latitude and longitude values will be indexed; the third dimension is ignored. If `false`,
geo-points containing any more than latitude and longitude (two dimensions) values throw an exception
and reject the whole document.
| `true`
|=======================================================================

View File

@ -25,15 +25,17 @@ import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.util.BitUtil;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentFragment;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Strings;
import java.io.IOException;
import java.util.Arrays;
import static org.elasticsearch.common.geo.GeoHashUtils.mortonEncode;
import static org.elasticsearch.common.geo.GeoHashUtils.stringEncode;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
public final class GeoPoint implements ToXContentFragment {
@ -79,14 +81,24 @@ public final class GeoPoint implements ToXContentFragment {
}
public GeoPoint resetFromString(String value) {
int comma = value.indexOf(',');
if (comma != -1) {
lat = Double.parseDouble(value.substring(0, comma).trim());
lon = Double.parseDouble(value.substring(comma + 1).trim());
} else {
resetFromGeoHash(value);
return resetFromString(value, false);
}
public GeoPoint resetFromString(String value, final boolean ignoreZValue) {
if (value.contains(",")) {
String[] vals = value.split(",");
if (vals.length > 3) {
throw new ElasticsearchParseException("failed to parse [{}], expected 2 or 3 coordinates "
+ "but found: [{}]", vals.length);
}
double lat = Double.parseDouble(vals[0].trim());
double lon = Double.parseDouble(vals[1].trim());
if (vals.length > 2) {
GeoPoint.assertZValue(ignoreZValue, Double.parseDouble(vals[2].trim()));
}
return reset(lat, lon);
}
return this;
return resetFromGeoHash(value);
}
public GeoPoint resetFromIndexHash(long hash) {
@ -193,4 +205,12 @@ public final class GeoPoint implements ToXContentFragment {
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.latlon(lat, lon);
}
public static double assertZValue(final boolean ignoreZValue, double zValue) {
if (ignoreZValue == false) {
throw new ElasticsearchParseException("Exception parsing coordinates: found Z value [{}] but [{}] "
+ "parameter is [{}]", zValue, IGNORE_Z_VALUE, ignoreZValue);
}
return zValue;
}
}

View File

@ -24,6 +24,7 @@ import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.util.SloppyMath;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
@ -345,6 +346,11 @@ public class GeoUtils {
return parseGeoPoint(parser, new GeoPoint());
}
public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point) throws IOException, ElasticsearchParseException {
return parseGeoPoint(parser, point, false);
}
/**
* Parse a {@link GeoPoint} with a {@link XContentParser}. A geopoint has one of the following forms:
*
@ -359,7 +365,8 @@ public class GeoUtils {
* @param point A {@link GeoPoint} that will be reset by the values parsed
* @return new {@link GeoPoint} parsed from the parse
*/
public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point) throws IOException, ElasticsearchParseException {
public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
double lat = Double.NaN;
double lon = Double.NaN;
String geohash = null;
@ -438,7 +445,7 @@ public class GeoUtils {
} else if(element == 2) {
lat = parser.doubleValue();
} else {
throw new ElasticsearchParseException("only two values allowed");
GeoPoint.assertZValue(ignoreZValue, parser.doubleValue());
}
} else {
throw new ElasticsearchParseException("numeric value expected");
@ -446,25 +453,12 @@ public class GeoUtils {
}
return point.reset(lat, lon);
} else if(parser.currentToken() == Token.VALUE_STRING) {
String data = parser.text();
return parseGeoPoint(data, point);
return point.resetFromString(parser.text(), ignoreZValue);
} else {
throw new ElasticsearchParseException("geo_point expected");
}
}
/** parse a {@link GeoPoint} from a String */
public static GeoPoint parseGeoPoint(String data, GeoPoint point) {
int comma = data.indexOf(',');
if(comma > 0) {
double lat = Double.parseDouble(data.substring(0, comma).trim());
double lon = Double.parseDouble(data.substring(comma + 1).trim());
return point.reset(lat, lon);
} else {
return point.resetFromGeoHash(data);
}
}
/** Returns the maximum distance/radius (in meters) from the point 'center' before overlapping */
public static double maxRadialDistanceMeters(final double centerLat, final double centerLon) {
if (Math.abs(centerLat) == MAX_LAT) {

View File

@ -173,6 +173,10 @@ public class CircleBuilder extends ShapeBuilder<Circle, CircleBuilder> {
throw new UnsupportedOperationException("The WKT spec does not support CIRCLE geometry");
}
public int numDimensions() {
return Double.isNaN(center.z) ? 2 : 3;
}
@Override
public int hashCode() {
return Objects.hash(center, radius, unit.ordinal());

View File

@ -20,6 +20,7 @@
package org.elasticsearch.common.geo.builders;
import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.ElasticsearchException;
import java.util.ArrayList;
import java.util.Arrays;
@ -41,7 +42,16 @@ public class CoordinatesBuilder {
* @return this
*/
public CoordinatesBuilder coordinate(Coordinate coordinate) {
this.points.add(coordinate);
int expectedDims;
int actualDims;
if (points.isEmpty() == false
&& (expectedDims = Double.isNaN(points.get(0).z) ? 2 : 3) != (actualDims = Double.isNaN(coordinate.z) ? 2 : 3)) {
throw new ElasticsearchException("unable to add coordinate to CoordinateBuilder: " +
"coordinate dimensions do not match. Expected [{}] but found [{}]", expectedDims, actualDims);
} else {
this.points.add(coordinate);
}
return this;
}

View File

@ -45,6 +45,9 @@ public class EnvelopeBuilder extends ShapeBuilder<Rectangle, EnvelopeBuilder> {
public EnvelopeBuilder(Coordinate topLeft, Coordinate bottomRight) {
Objects.requireNonNull(topLeft, "topLeft of envelope cannot be null");
Objects.requireNonNull(bottomRight, "bottomRight of envelope cannot be null");
if (Double.isNaN(topLeft.z) != Double.isNaN(bottomRight.z)) {
throw new IllegalArgumentException("expected same number of dimensions for topLeft and bottomRight");
}
this.topLeft = topLeft;
this.bottomRight = bottomRight;
}
@ -114,6 +117,11 @@ public class EnvelopeBuilder extends ShapeBuilder<Rectangle, EnvelopeBuilder> {
return TYPE;
}
@Override
public int numDimensions() {
return Double.isNaN(topLeft.z) ? 2 : 3;
}
@Override
public int hashCode() {
return Objects.hash(topLeft, bottomRight);

View File

@ -159,6 +159,15 @@ public class GeometryCollectionBuilder extends ShapeBuilder {
return TYPE;
}
@Override
public int numDimensions() {
if (shapes == null || shapes.isEmpty()) {
throw new IllegalStateException("unable to get number of dimensions, " +
"GeometryCollection has not yet been initialized");
}
return shapes.get(0).numDimensions();
}
@Override
public Shape build() {

View File

@ -91,6 +91,15 @@ public class LineStringBuilder extends ShapeBuilder<JtsGeometry, LineStringBuild
return TYPE;
}
@Override
public int numDimensions() {
if (coordinates == null || coordinates.isEmpty()) {
throw new IllegalStateException("unable to get number of dimensions, " +
"LineString has not yet been initialized");
}
return Double.isNaN(coordinates.get(0).z) ? 2 : 3;
}
@Override
public JtsGeometry build() {
Coordinate[] coordinates = this.coordinates.toArray(new Coordinate[this.coordinates.size()]);

View File

@ -101,6 +101,14 @@ public class MultiLineStringBuilder extends ShapeBuilder<JtsGeometry, MultiLineS
return sb;
}
public int numDimensions() {
if (lines == null || lines.isEmpty()) {
throw new IllegalStateException("unable to get number of dimensions, " +
"LineStrings have not yet been initialized");
}
return lines.get(0).numDimensions();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -80,4 +80,13 @@ public class MultiPointBuilder extends ShapeBuilder<XShapeCollection<Point>, Mul
public GeoShapeType type() {
return TYPE;
}
@Override
public int numDimensions() {
if (coordinates == null || coordinates.isEmpty()) {
throw new IllegalStateException("unable to get number of dimensions, " +
"LineString has not yet been initialized");
}
return Double.isNaN(coordinates.get(0).z) ? 2 : 3;
}
}

View File

@ -153,6 +153,15 @@ public class MultiPolygonBuilder extends ShapeBuilder {
return TYPE;
}
@Override
public int numDimensions() {
if (polygons == null || polygons.isEmpty()) {
throw new IllegalStateException("unable to get number of dimensions, " +
"Polygons have not yet been initialized");
}
return polygons.get(0).numDimensions();
}
@Override
public Shape build() {

View File

@ -93,4 +93,9 @@ public class PointBuilder extends ShapeBuilder<Point, PointBuilder> {
public GeoShapeType type() {
return TYPE;
}
@Override
public int numDimensions() {
return Double.isNaN(coordinates.get(0).z) ? 2 : 3;
}
}

View File

@ -283,6 +283,15 @@ public class PolygonBuilder extends ShapeBuilder<JtsGeometry, PolygonBuilder> {
return TYPE;
}
@Override
public int numDimensions() {
if (shell == null) {
throw new IllegalStateException("unable to get number of dimensions, " +
"Polygon has not yet been initialized");
}
return shell.numDimensions();
}
protected static Polygon polygon(GeometryFactory factory, Coordinate[][] polygon) {
LinearRing shell = factory.createLinearRing(polygon[0]);
LinearRing[] holes;

View File

@ -25,6 +25,7 @@ import com.vividsolutions.jts.geom.GeometryFactory;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Assertions;
import org.elasticsearch.Version;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.GeoWKTParser;
@ -109,7 +110,13 @@ public abstract class ShapeBuilder<T extends Shape, E extends ShapeBuilder<T,E>>
}
protected static Coordinate readFromStream(StreamInput in) throws IOException {
return new Coordinate(in.readDouble(), in.readDouble());
double x = in.readDouble();
double y = in.readDouble();
Double z = null;
if (in.getVersion().onOrAfter(Version.V_6_3_0)) {
z = in.readOptionalDouble();
}
return z == null ? new Coordinate(x, y) : new Coordinate(x, y, z);
}
@Override
@ -123,6 +130,9 @@ public abstract class ShapeBuilder<T extends Shape, E extends ShapeBuilder<T,E>>
protected static void writeCoordinateTo(Coordinate coordinate, StreamOutput out) throws IOException {
out.writeDouble(coordinate.x);
out.writeDouble(coordinate.y);
if (out.getVersion().onOrAfter(Version.V_6_3_0)) {
out.writeOptionalDouble(Double.isNaN(coordinate.z) ? null : coordinate.z);
}
}
@SuppressWarnings("unchecked")
@ -217,6 +227,9 @@ public abstract class ShapeBuilder<T extends Shape, E extends ShapeBuilder<T,E>>
*/
public abstract GeoShapeType type();
/** tracks number of dimensions for this shape */
public abstract int numDimensions();
/**
* Calculate the intersection of a line segment and a vertical dateline.
*
@ -429,7 +442,11 @@ public abstract class ShapeBuilder<T extends Shape, E extends ShapeBuilder<T,E>>
}
protected static XContentBuilder toXContent(XContentBuilder builder, Coordinate coordinate) throws IOException {
return builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
builder.startArray().value(coordinate.x).value(coordinate.y);
if (Double.isNaN(coordinate.z) == false) {
builder.value(coordinate.z);
}
return builder.endArray();
}
/**

View File

@ -19,6 +19,7 @@
package org.elasticsearch.common.geo.parsers;
import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -61,6 +62,16 @@ public class CoordinateNode implements ToXContentObject {
return (coordinate == null && (children == null || children.isEmpty()));
}
protected int numDimensions() {
if (isEmpty()) {
throw new ElasticsearchException("attempting to get number of dimensions on an empty coordinate node");
}
if (coordinate != null) {
return Double.isNaN(coordinate.z) ? 2 : 3;
}
return children.get(0).numDimensions();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
if (children == null) {

View File

@ -21,6 +21,7 @@ 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.GeoPoint;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.builders.CircleBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
@ -49,6 +50,7 @@ abstract class GeoJsonParser {
ShapeBuilder.Orientation requestedOrientation =
(shapeMapper == null) ? ShapeBuilder.Orientation.RIGHT : shapeMapper.fieldType().orientation();
Explicit<Boolean> coerce = (shapeMapper == null) ? GeoShapeFieldMapper.Defaults.COERCE : shapeMapper.coerce();
Explicit<Boolean> ignoreZValue = (shapeMapper == null) ? GeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE : shapeMapper.ignoreZValue();
String malformedException = null;
@ -68,7 +70,12 @@ abstract class GeoJsonParser {
}
} else if (ShapeParser.FIELD_COORDINATES.match(fieldName, parser.getDeprecationHandler())) {
parser.nextToken();
coordinateNode = parseCoordinates(parser);
CoordinateNode tempNode = parseCoordinates(parser, ignoreZValue.value());
if (coordinateNode != null && tempNode.numDimensions() != coordinateNode.numDimensions()) {
throw new ElasticsearchParseException("Exception parsing coordinates: " +
"number of dimensions do not match");
}
coordinateNode = tempNode;
} else if (ShapeParser.FIELD_GEOMETRIES.match(fieldName, parser.getDeprecationHandler())) {
if (shapeType == null) {
shapeType = GeoShapeType.GEOMETRYCOLLECTION;
@ -136,36 +143,46 @@ abstract class GeoJsonParser {
* Thrown if an error occurs while reading from the
* XContentParser
*/
private static CoordinateNode parseCoordinates(XContentParser parser) throws IOException {
private static CoordinateNode parseCoordinates(XContentParser parser, boolean ignoreZValue) 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));
return new CoordinateNode(parseCoordinate(parser, ignoreZValue));
} 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));
CoordinateNode node = parseCoordinates(parser, ignoreZValue);
if (nodes.isEmpty() == false && nodes.get(0).numDimensions() != node.numDimensions()) {
throw new ElasticsearchParseException("Exception parsing coordinates: number of dimensions do not match");
}
nodes.add(node);
token = parser.nextToken();
}
return new CoordinateNode(nodes);
}
private static Coordinate parseCoordinate(XContentParser parser) throws IOException {
private static Coordinate parseCoordinate(XContentParser parser, boolean ignoreZValue) 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();
// alt (for storing purposes only - future use includes 3d shapes)
double alt = Double.NaN;
if (token == XContentParser.Token.VALUE_NUMBER) {
alt = GeoPoint.assertZValue(ignoreZValue, parser.doubleValue());
parser.nextToken();
}
// todo support z/alt
return new Coordinate(lon, lat);
// do not support > 3 dimensions
if (parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
throw new ElasticsearchParseException("geo coordinates greater than 3 dimensions are not supported");
}
return new Coordinate(lon, lat, alt);
}
/**

View File

@ -20,6 +20,7 @@ package org.elasticsearch.common.geo.parsers;
import com.vividsolutions.jts.geom.Coordinate;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoShapeType;
import java.io.StringReader;
@ -35,6 +36,7 @@ import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import java.io.IOException;
import java.io.StreamTokenizer;
@ -52,7 +54,7 @@ public class GeoWKTParser {
public static final String LPAREN = "(";
public static final String RPAREN = ")";
public static final String COMMA = ",";
private static final String NAN = "NaN";
public static final String NAN = "NaN";
private static final String NUMBER = "<NUMBER>";
private static final String EOF = "END-OF-STREAM";
@ -61,16 +63,23 @@ public class GeoWKTParser {
// no instance
private GeoWKTParser() {}
public static ShapeBuilder parse(XContentParser parser)
public static ShapeBuilder parse(XContentParser parser, final GeoShapeFieldMapper shapeMapper)
throws IOException, ElasticsearchParseException {
return parseExpectedType(parser, null);
return parseExpectedType(parser, null, shapeMapper);
}
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType)
throws IOException, ElasticsearchParseException {
return parseExpectedType(parser, shapeType, null);
}
/** throws an exception if the parsed geometry type does not match the expected shape type */
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType)
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType,
final GeoShapeFieldMapper shapeMapper)
throws IOException, ElasticsearchParseException {
StringReader reader = new StringReader(parser.text());
try {
boolean ignoreZValue = (shapeMapper != null && shapeMapper.ignoreZValue().value() == true);
// setup the tokenizer; configured to read words w/o numbers
StreamTokenizer tokenizer = new StreamTokenizer(reader);
tokenizer.resetSyntax();
@ -83,7 +92,7 @@ public class GeoWKTParser {
tokenizer.wordChars('.', '.');
tokenizer.whitespaceChars(0, ' ');
tokenizer.commentChar('#');
ShapeBuilder builder = parseGeometry(tokenizer, shapeType);
ShapeBuilder builder = parseGeometry(tokenizer, shapeType, ignoreZValue);
checkEOF(tokenizer);
return builder;
} finally {
@ -92,7 +101,7 @@ public class GeoWKTParser {
}
/** parse geometry from the stream tokenizer */
private static ShapeBuilder parseGeometry(StreamTokenizer stream, GeoShapeType shapeType)
private static ShapeBuilder parseGeometry(StreamTokenizer stream, GeoShapeType shapeType, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
final GeoShapeType type = GeoShapeType.forName(nextWord(stream));
if (shapeType != null && shapeType != GeoShapeType.GEOMETRYCOLLECTION) {
@ -102,21 +111,21 @@ public class GeoWKTParser {
}
switch (type) {
case POINT:
return parsePoint(stream);
return parsePoint(stream, ignoreZValue);
case MULTIPOINT:
return parseMultiPoint(stream);
return parseMultiPoint(stream, ignoreZValue);
case LINESTRING:
return parseLine(stream);
return parseLine(stream, ignoreZValue);
case MULTILINESTRING:
return parseMultiLine(stream);
return parseMultiLine(stream, ignoreZValue);
case POLYGON:
return parsePolygon(stream);
return parsePolygon(stream, ignoreZValue);
case MULTIPOLYGON:
return parseMultiPolygon(stream);
return parseMultiPolygon(stream, ignoreZValue);
case ENVELOPE:
return parseBBox(stream);
case GEOMETRYCOLLECTION:
return parseGeometryCollection(stream);
return parseGeometryCollection(stream, ignoreZValue);
default:
throw new IllegalArgumentException("Unknown geometry type: " + type);
}
@ -137,24 +146,25 @@ public class GeoWKTParser {
return new EnvelopeBuilder(new Coordinate(minLon, maxLat), new Coordinate(maxLon, minLat));
}
private static PointBuilder parsePoint(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
private static PointBuilder parsePoint(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
PointBuilder pt = new PointBuilder(nextNumber(stream), nextNumber(stream));
if (isNumberNext(stream) == true) {
nextNumber(stream);
GeoPoint.assertZValue(ignoreZValue, nextNumber(stream));
}
nextCloser(stream);
return pt;
}
private static List<Coordinate> parseCoordinateList(StreamTokenizer stream)
private static List<Coordinate> parseCoordinateList(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
CoordinatesBuilder coordinates = new CoordinatesBuilder();
boolean isOpenParen = false;
if (isNumberNext(stream) || (isOpenParen = nextWord(stream).equals(LPAREN))) {
coordinates.coordinate(parseCoordinate(stream));
coordinates.coordinate(parseCoordinate(stream, ignoreZValue));
}
if (isOpenParen && nextCloser(stream).equals(RPAREN) == false) {
@ -164,7 +174,7 @@ public class GeoWKTParser {
while (nextCloserOrComma(stream).equals(COMMA)) {
isOpenParen = false;
if (isNumberNext(stream) || (isOpenParen = nextWord(stream).equals(LPAREN))) {
coordinates.coordinate(parseCoordinate(stream));
coordinates.coordinate(parseCoordinate(stream, ignoreZValue));
}
if (isOpenParen && nextCloser(stream).equals(RPAREN) == false) {
throw new ElasticsearchParseException("expected: " + RPAREN + " but found: " + tokenString(stream), stream.lineno());
@ -173,77 +183,82 @@ public class GeoWKTParser {
return coordinates.build();
}
private static Coordinate parseCoordinate(StreamTokenizer stream)
private static Coordinate parseCoordinate(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
final double lon = nextNumber(stream);
final double lat = nextNumber(stream);
Double z = null;
if (isNumberNext(stream)) {
z = nextNumber(stream);
z = GeoPoint.assertZValue(ignoreZValue, nextNumber(stream));
}
return z == null ? new Coordinate(lon, lat) : new Coordinate(lon, lat, z);
}
private static MultiPointBuilder parseMultiPoint(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
private static MultiPointBuilder parseMultiPoint(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return null;
}
return new MultiPointBuilder(parseCoordinateList(stream));
return new MultiPointBuilder(parseCoordinateList(stream, ignoreZValue));
}
private static LineStringBuilder parseLine(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
private static LineStringBuilder parseLine(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return null;
}
return new LineStringBuilder(parseCoordinateList(stream));
return new LineStringBuilder(parseCoordinateList(stream, ignoreZValue));
}
private static MultiLineStringBuilder parseMultiLine(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
private static MultiLineStringBuilder parseMultiLine(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return null;
}
MultiLineStringBuilder builder = new MultiLineStringBuilder();
builder.linestring(parseLine(stream));
builder.linestring(parseLine(stream, ignoreZValue));
while (nextCloserOrComma(stream).equals(COMMA)) {
builder.linestring(parseLine(stream));
builder.linestring(parseLine(stream, ignoreZValue));
}
return builder;
}
private static PolygonBuilder parsePolygon(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
private static PolygonBuilder parsePolygon(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
PolygonBuilder builder = new PolygonBuilder(parseLine(stream), ShapeBuilder.Orientation.RIGHT);
PolygonBuilder builder = new PolygonBuilder(parseLine(stream, ignoreZValue), ShapeBuilder.Orientation.RIGHT);
while (nextCloserOrComma(stream).equals(COMMA)) {
builder.hole(parseLine(stream));
builder.hole(parseLine(stream, ignoreZValue));
}
return builder;
}
private static MultiPolygonBuilder parseMultiPolygon(StreamTokenizer stream) throws IOException, ElasticsearchParseException {
private static MultiPolygonBuilder parseMultiPolygon(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
MultiPolygonBuilder builder = new MultiPolygonBuilder().polygon(parsePolygon(stream));
MultiPolygonBuilder builder = new MultiPolygonBuilder().polygon(parsePolygon(stream, ignoreZValue));
while (nextCloserOrComma(stream).equals(COMMA)) {
builder.polygon(parsePolygon(stream));
builder.polygon(parsePolygon(stream, ignoreZValue));
}
return builder;
}
private static GeometryCollectionBuilder parseGeometryCollection(StreamTokenizer stream)
private static GeometryCollectionBuilder parseGeometryCollection(StreamTokenizer stream, final boolean ignoreZValue)
throws IOException, ElasticsearchParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return null;
}
GeometryCollectionBuilder builder = new GeometryCollectionBuilder().shape(
parseGeometry(stream, GeoShapeType.GEOMETRYCOLLECTION));
parseGeometry(stream, GeoShapeType.GEOMETRYCOLLECTION, ignoreZValue));
while (nextCloserOrComma(stream).equals(COMMA)) {
builder.shape(parseGeometry(stream, null));
builder.shape(parseGeometry(stream, null, ignoreZValue));
}
return builder;
}

View File

@ -23,6 +23,7 @@ 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.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import java.io.IOException;
@ -52,7 +53,7 @@ public interface ShapeParser {
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
return GeoJsonParser.parse(parser, shapeMapper);
} else if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
return GeoWKTParser.parse(parser);
return GeoWKTParser.parse(parser, shapeMapper);
}
throw new ElasticsearchParseException("shape must be an object consisting of type and coordinates");
}

View File

@ -29,6 +29,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.settings.Settings;
@ -57,11 +58,13 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
public static class Names {
public static final String IGNORE_MALFORMED = "ignore_malformed";
public static final ParseField IGNORE_Z_VALUE = new ParseField("ignore_z_value");
}
public static class Defaults {
public static final Explicit<Boolean> IGNORE_MALFORMED = new Explicit<>(false, false);
public static final GeoPointFieldType FIELD_TYPE = new GeoPointFieldType();
public static final Explicit<Boolean> IGNORE_Z_VALUE = new Explicit<>(true, false);
static {
FIELD_TYPE.setTokenized(false);
@ -73,6 +76,7 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
public static class Builder extends FieldMapper.Builder<Builder, GeoPointFieldMapper> {
protected Boolean ignoreMalformed;
private Boolean ignoreZValue;
public Builder(String name) {
super(name, Defaults.FIELD_TYPE, Defaults.FIELD_TYPE);
@ -94,19 +98,32 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
return GeoPointFieldMapper.Defaults.IGNORE_MALFORMED;
}
protected Explicit<Boolean> ignoreZValue(BuilderContext context) {
if (ignoreZValue != null) {
return new Explicit<>(ignoreZValue, true);
}
return Defaults.IGNORE_Z_VALUE;
}
public Builder ignoreZValue(final boolean ignoreZValue) {
this.ignoreZValue = ignoreZValue;
return this;
}
public GeoPointFieldMapper build(BuilderContext context, String simpleName, MappedFieldType fieldType,
MappedFieldType defaultFieldType, Settings indexSettings,
MultiFields multiFields, Explicit<Boolean> ignoreMalformed,
CopyTo copyTo) {
Explicit<Boolean> ignoreZValue, CopyTo copyTo) {
setupFieldType(context);
return new GeoPointFieldMapper(simpleName, fieldType, defaultFieldType, indexSettings, multiFields,
ignoreMalformed, copyTo);
ignoreMalformed, ignoreZValue, copyTo);
}
@Override
public GeoPointFieldMapper build(BuilderContext context) {
return build(context, name, fieldType, defaultFieldType, context.indexSettings(),
multiFieldsBuilder.build(this, context), ignoreMalformed(context), copyTo);
multiFieldsBuilder.build(this, context), ignoreMalformed(context),
ignoreZValue(context), copyTo);
}
}
@ -125,6 +142,10 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
if (propName.equals(Names.IGNORE_MALFORMED)) {
builder.ignoreMalformed(TypeParsers.nodeBooleanValue(name, Names.IGNORE_MALFORMED, propNode, parserContext));
iterator.remove();
} else if (propName.equals(Names.IGNORE_Z_VALUE.getPreferredName())) {
builder.ignoreZValue(TypeParsers.nodeBooleanValue(propName, Names.IGNORE_Z_VALUE.getPreferredName(),
propNode, parserContext));
iterator.remove();
}
}
@ -133,12 +154,14 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
}
protected Explicit<Boolean> ignoreMalformed;
protected Explicit<Boolean> ignoreZValue;
public GeoPointFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
Settings indexSettings, MultiFields multiFields, Explicit<Boolean> ignoreMalformed,
CopyTo copyTo) {
Explicit<Boolean> ignoreZValue, CopyTo copyTo) {
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, copyTo);
this.ignoreMalformed = ignoreMalformed;
this.ignoreZValue = ignoreZValue;
}
@Override
@ -148,6 +171,9 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
if (gpfmMergeWith.ignoreMalformed.explicit()) {
this.ignoreMalformed = gpfmMergeWith.ignoreMalformed;
}
if (gpfmMergeWith.ignoreZValue.explicit()) {
this.ignoreZValue = gpfmMergeWith.ignoreZValue;
}
}
@Override
@ -264,12 +290,18 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
double lon = context.parser().doubleValue();
token = context.parser().nextToken();
double lat = context.parser().doubleValue();
while ((token = context.parser().nextToken()) != XContentParser.Token.END_ARRAY);
token = context.parser().nextToken();
Double alt = Double.NaN;
if (token == XContentParser.Token.VALUE_NUMBER) {
alt = GeoPoint.assertZValue(ignoreZValue.value(), context.parser().doubleValue());
} else if (token != XContentParser.Token.END_ARRAY) {
throw new ElasticsearchParseException("[{}] field type does not accept > 3 dimensions", CONTENT_TYPE);
}
parse(context, sparse.reset(lat, lon));
} else {
while (token != XContentParser.Token.END_ARRAY) {
if (token == XContentParser.Token.VALUE_STRING) {
parsePointFromString(context, sparse, context.parser().text());
parse(context, sparse.resetFromString(context.parser().text(), ignoreZValue.value()));
} else {
try {
parse(context, GeoUtils.parseGeoPoint(context.parser(), sparse));
@ -284,7 +316,7 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
}
}
} else if (token == XContentParser.Token.VALUE_STRING) {
parsePointFromString(context, sparse, context.parser().text());
parse(context, sparse.resetFromString(context.parser().text(), ignoreZValue.value()));
} else if (token != XContentParser.Token.VALUE_NULL) {
try {
parse(context, GeoUtils.parseGeoPoint(context.parser(), sparse));
@ -300,19 +332,18 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper
return null;
}
private void parsePointFromString(ParseContext context, GeoPoint sparse, String point) throws IOException {
if (point.indexOf(',') < 0) {
parse(context, sparse.resetFromGeoHash(point));
} else {
parse(context, sparse.resetFromString(point));
}
}
@Override
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
super.doXContentBody(builder, includeDefaults, params);
if (includeDefaults || ignoreMalformed.explicit()) {
builder.field(GeoPointFieldMapper.Names.IGNORE_MALFORMED, ignoreMalformed.value());
}
if (includeDefaults || ignoreZValue.explicit()) {
builder.field(Names.IGNORE_Z_VALUE.getPreferredName(), ignoreZValue.value());
}
}
public Explicit<Boolean> ignoreZValue() {
return ignoreZValue;
}
}

View File

@ -101,6 +101,7 @@ public class GeoShapeFieldMapper extends FieldMapper {
public static final double LEGACY_DISTANCE_ERROR_PCT = 0.025d;
public static final Explicit<Boolean> COERCE = new Explicit<>(false, false);
public static final Explicit<Boolean> IGNORE_MALFORMED = new Explicit<>(false, false);
public static final Explicit<Boolean> IGNORE_Z_VALUE = new Explicit<>(true, false);
public static final MappedFieldType FIELD_TYPE = new GeoShapeFieldType();
@ -121,6 +122,7 @@ public class GeoShapeFieldMapper extends FieldMapper {
private Boolean coerce;
private Boolean ignoreMalformed;
private Boolean ignoreZValue;
public Builder(String name) {
super(name, Defaults.FIELD_TYPE, Defaults.FIELD_TYPE);
@ -166,6 +168,18 @@ public class GeoShapeFieldMapper extends FieldMapper {
return Defaults.IGNORE_MALFORMED;
}
protected Explicit<Boolean> ignoreZValue(BuilderContext context) {
if (ignoreZValue != null) {
return new Explicit<>(ignoreZValue, true);
}
return Defaults.IGNORE_Z_VALUE;
}
public Builder ignoreZValue(final boolean ignoreZValue) {
this.ignoreZValue = ignoreZValue;
return this;
}
@Override
public GeoShapeFieldMapper build(BuilderContext context) {
GeoShapeFieldType geoShapeFieldType = (GeoShapeFieldType)fieldType;
@ -175,8 +189,8 @@ public class GeoShapeFieldMapper extends FieldMapper {
}
setupFieldType(context);
return new GeoShapeFieldMapper(name, fieldType, ignoreMalformed(context), coerce(context), context.indexSettings(),
multiFieldsBuilder.build(this, context), copyTo);
return new GeoShapeFieldMapper(name, fieldType, ignoreMalformed(context), coerce(context), ignoreZValue(context),
context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
}
}
@ -213,6 +227,10 @@ public class GeoShapeFieldMapper extends FieldMapper {
} else if (Names.COERCE.equals(fieldName)) {
builder.coerce(TypeParsers.nodeBooleanValue(fieldName, Names.COERCE, fieldNode, parserContext));
iterator.remove();
} else if (GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName().equals(fieldName)) {
builder.ignoreZValue(TypeParsers.nodeBooleanValue(fieldName, GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName(),
fieldNode, parserContext));
iterator.remove();
} else if (Names.STRATEGY_POINTS_ONLY.equals(fieldName)
&& builder.fieldType().strategyName.equals(SpatialStrategy.TERM.getStrategyName()) == false) {
boolean pointsOnly = TypeParsers.nodeBooleanValue(fieldName, Names.STRATEGY_POINTS_ONLY, fieldNode, parserContext);
@ -444,12 +462,15 @@ public class GeoShapeFieldMapper extends FieldMapper {
protected Explicit<Boolean> coerce;
protected Explicit<Boolean> ignoreMalformed;
protected Explicit<Boolean> ignoreZValue;
public GeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, Explicit<Boolean> ignoreMalformed,
Explicit<Boolean> coerce, Settings indexSettings, MultiFields multiFields, CopyTo copyTo) {
Explicit<Boolean> coerce, Explicit<Boolean> ignoreZValue, Settings indexSettings,
MultiFields multiFields, CopyTo copyTo) {
super(simpleName, fieldType, Defaults.FIELD_TYPE, indexSettings, multiFields, copyTo);
this.coerce = coerce;
this.ignoreMalformed = ignoreMalformed;
this.ignoreZValue = ignoreZValue;
}
@Override
@ -513,6 +534,9 @@ public class GeoShapeFieldMapper extends FieldMapper {
if (gsfm.ignoreMalformed.explicit()) {
this.ignoreMalformed = gsfm.ignoreMalformed;
}
if (gsfm.ignoreZValue.explicit()) {
this.ignoreZValue = gsfm.ignoreZValue;
}
}
@Override
@ -546,6 +570,9 @@ public class GeoShapeFieldMapper extends FieldMapper {
if (includeDefaults || ignoreMalformed.explicit()) {
builder.field(IGNORE_MALFORMED, ignoreMalformed.value());
}
if (includeDefaults || ignoreZValue.explicit()) {
builder.field(GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName(), ignoreZValue.value());
}
}
public Explicit<Boolean> coerce() {
@ -556,6 +583,10 @@ public class GeoShapeFieldMapper extends FieldMapper {
return ignoreMalformed;
}
public Explicit<Boolean> ignoreZValue() {
return ignoreZValue;
}
@Override
protected String contentType() {
return CONTENT_TYPE;

View File

@ -263,7 +263,7 @@ public class ValuesSourceConfig<VS extends ValuesSource> {
return (VS) MissingValues.replaceMissing((ValuesSource.Numeric) vs, missing);
} else if (vs instanceof ValuesSource.GeoPoint) {
// TODO: also support the structured formats of geo points
final GeoPoint missing = GeoUtils.parseGeoPoint(missing().toString(), new GeoPoint());
final GeoPoint missing = new GeoPoint(missing().toString());
return (VS) MissingValues.replaceMissing((ValuesSource.GeoPoint) vs, missing);
} else {
// Should not happen

View File

@ -133,7 +133,7 @@ public class GeoContextMapping extends ContextMapping<GeoQueryContext> {
* <li>String/Object/Array: <pre>&quot;GEO POINT&quot;</pre></li>
* </ul>
*
* see {@link GeoUtils#parseGeoPoint(String, GeoPoint)} for GEO POINT
* see {@code GeoPoint(String)} for GEO POINT
*/
@Override
public Set<CharSequence> parseContext(ParseContext parseContext, XContentParser parser) throws IOException, ElasticsearchParseException {
@ -249,7 +249,7 @@ public class GeoContextMapping extends ContextMapping<GeoQueryContext> {
* </ul>
* <li>String: <pre>GEO POINT</pre></li>
* </ul>
* see {@link GeoUtils#parseGeoPoint(String, GeoPoint)} for GEO POINT
* see {@code GeoPoint(String)} for GEO POINT
*/
@Override
public List<InternalQueryContext> toInternalQueryContexts(List<GeoQueryContext> queryContexts) {

View File

@ -28,11 +28,18 @@ import com.vividsolutions.jts.geom.Polygon;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.settings.Settings;
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.index.mapper.ContentPath;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Circle;
@ -135,8 +142,9 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
.startArray("coordinates").value(100.0).value(0.0).value(15.0).value(18.0).endArray()
.endObject();
Point expectedPt = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
assertGeometryEquals(new JtsPoint(expectedPt, SPATIAL_CONTEXT), pointGeoJson);
XContentParser parser = createParser(pointGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// multi dimension linestring
XContentBuilder lineGeoJson = XContentFactory.jsonBuilder()
@ -148,13 +156,9 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
.endArray()
.endObject();
List<Coordinate> lineCoordinates = new ArrayList<>();
lineCoordinates.add(new Coordinate(100, 0));
lineCoordinates.add(new Coordinate(101, 1));
LineString expectedLS = GEOMETRY_FACTORY.createLineString(
lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
assertGeometryEquals(jtsGeom(expectedLS), lineGeoJson);
parser = createParser(lineGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
}
@Override
@ -231,6 +235,61 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
assertGeometryEquals(jtsGeom(expected), polygonGeoJson);
}
public void testParse3DPolygon() throws IOException {
XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder()
.startObject()
.field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(100.0).value(1.0).value(10.0).endArray()
.startArray().value(101.0).value(1.0).value(10.0).endArray()
.startArray().value(101.0).value(0.0).value(10.0).endArray()
.startArray().value(100.0).value(0.0).value(10.0).endArray()
.startArray().value(100.0).value(1.0).value(10.0).endArray()
.endArray()
.endArray()
.endObject();
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 0, 10));
shellCoordinates.add(new Coordinate(101, 0, 10));
shellCoordinates.add(new Coordinate(101, 1, 10));
shellCoordinates.add(new Coordinate(100, 1, 10));
shellCoordinates.add(new Coordinate(100, 0, 10));
Settings indexSettings = Settings.builder()
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_6_3_0)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext);
XContentParser parser = createParser(polygonGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertEquals(jtsGeom(expected), ShapeParser.parse(parser, mapperBuilder).build());
}
public void testInvalidDimensionalPolygon() throws IOException {
XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder()
.startObject()
.field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(100.0).value(1.0).value(10.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.startArray().value(101.0).value(0.0).value(10.0).endArray()
.startArray().value(100.0).value(0.0).value(10.0).endArray()
.startArray().value(100.0).value(1.0).value(10.0).endArray()
.endArray()
.endArray()
.endObject();
XContentParser parser = createParser(polygonGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
}
public void testParseInvalidPoint() throws IOException {
// test case 1: create an invalid point object with multipoint data format
XContentBuilder invalidPoint1 = XContentFactory.jsonBuilder()
@ -326,6 +385,46 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class);
}
public void testParseInvalidDimensionalMultiPolygon() throws IOException {
// test invalid multipolygon (an "accidental" polygon with inner rings outside outer ring)
String multiPolygonGeoJson = Strings.toString(XContentFactory.jsonBuilder()
.startObject()
.field("type", "MultiPolygon")
.startArray("coordinates")
.startArray()//first poly (without holes)
.startArray()
.startArray().value(102.0).value(2.0).endArray()
.startArray().value(103.0).value(2.0).endArray()
.startArray().value(103.0).value(3.0).endArray()
.startArray().value(102.0).value(3.0).endArray()
.startArray().value(102.0).value(2.0).endArray()
.endArray()
.endArray()
.startArray()//second poly (with hole)
.startArray()
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(101.0).value(0.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.startArray().value(100.0).value(1.0).endArray()
.startArray().value(100.0).value(0.0).endArray()
.endArray()
.startArray()//hole
.startArray().value(100.2).value(0.8).endArray()
.startArray().value(100.2).value(0.2).value(10.0).endArray()
.startArray().value(100.8).value(0.2).endArray()
.startArray().value(100.8).value(0.8).endArray()
.startArray().value(100.2).value(0.8).endArray()
.endArray()
.endArray()
.endArray()
.endObject());
XContentParser parser = createParser(JsonXContent.jsonXContent, multiPolygonGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
}
public void testParseOGCPolygonWithoutHoles() throws IOException {
// test 1: ccw poly not crossing dateline
String polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon")

View File

@ -25,7 +25,11 @@ 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.ElasticsearchException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
@ -37,9 +41,14 @@ 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.geo.parsers.ShapeParser;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.ContentPath;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.test.geo.RandomShapeGenerator;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Rectangle;
@ -80,7 +89,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
assertGeometryEquals(expected, xContentBuilder);
}
private void assertMalformed(Shape expected, ShapeBuilder builder) throws IOException {
private void assertMalformed(ShapeBuilder builder) throws IOException {
XContentBuilder xContentBuilder = toWKTContent(builder, true);
assertValidException(xContentBuilder, ElasticsearchParseException.class);
}
@ -91,7 +100,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
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));
assertMalformed(new PointBuilder().coordinate(c));
}
@Override
@ -107,7 +116,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
}
ShapeCollection expected = shapeCollection(shapes);
assertExpected(expected, new MultiPointBuilder(coordinates));
assertMalformed(expected, new MultiPointBuilder(coordinates));
assertMalformed(new MultiPointBuilder(coordinates));
}
private List<Coordinate> randomLineStringCoords() {
@ -142,7 +151,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(
lineStrings.toArray(new LineString[lineStrings.size()]));
assertExpected(jtsGeom(expected), builder);
assertMalformed(jtsGeom(expected), builder);
assertMalformed(builder);
}
@Override
@ -153,7 +162,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(coords);
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
assertExpected(jtsGeom(expected), builder);
assertMalformed(jtsGeom(expected), builder);
assertMalformed(builder);
}
@Override
@ -173,16 +182,16 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
}
Shape expected = shapeCollection(shapes);
assertExpected(expected, builder);
assertMalformed(expected, builder);
assertMalformed(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(100, 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, 1));
shellCoordinates.add(new Coordinate(100, 0));
List<Coordinate> holeCoordinates = new ArrayList<>();
@ -203,7 +212,110 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes);
assertExpected(jtsGeom(expected), polygonWithHole);
assertMalformed(jtsGeom(expected), polygonWithHole);
assertMalformed(polygonWithHole);
}
public void testParseMixedDimensionPolyWithHole() throws IOException {
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 0));
shellCoordinates.add(new Coordinate(101, 0));
shellCoordinates.add(new Coordinate(101, 1));
shellCoordinates.add(new Coordinate(100, 1));
shellCoordinates.add(new Coordinate(100, 0));
// add 3d point to test ISSUE #10501
List<Coordinate> holeCoordinates = new ArrayList<>();
holeCoordinates.add(new Coordinate(100.2, 0.2, 15.0));
holeCoordinates.add(new Coordinate(100.8, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.8, 10.0));
holeCoordinates.add(new Coordinate(100.2, 0.2));
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder().coordinates(shellCoordinates));
builder.hole(new LineStringBuilder(holeCoordinates));
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().value(builder.toWKT());
XContentParser parser = createParser(xContentBuilder);
parser.nextToken();
Settings indexSettings = Settings.builder()
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_6_3_0)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test").ignoreZValue(false).build(mockBuilderContext);
// test store z disabled
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
() -> ShapeParser.parse(parser, mapperBuilder));
assertThat(e, hasToString(containsString("but [ignore_z_value] parameter is [false]")));
}
public void testParseMixedDimensionPolyWithHoleStoredZ() throws IOException {
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 0));
shellCoordinates.add(new Coordinate(101, 0));
shellCoordinates.add(new Coordinate(101, 1));
shellCoordinates.add(new Coordinate(100, 1));
shellCoordinates.add(new Coordinate(100, 0));
// add 3d point to test ISSUE #10501
List<Coordinate> holeCoordinates = new ArrayList<>();
holeCoordinates.add(new Coordinate(100.2, 0.2, 15.0));
holeCoordinates.add(new Coordinate(100.8, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.8, 10.0));
holeCoordinates.add(new Coordinate(100.2, 0.2));
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder().coordinates(shellCoordinates));
builder.hole(new LineStringBuilder(holeCoordinates));
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().value(builder.toWKT());
XContentParser parser = createParser(xContentBuilder);
parser.nextToken();
Settings indexSettings = Settings.builder()
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_6_3_0)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext);
// test store z disabled
ElasticsearchException e = expectThrows(ElasticsearchException.class,
() -> ShapeParser.parse(parser, mapperBuilder));
assertThat(e, hasToString(containsString("unable to add coordinate to CoordinateBuilder: coordinate dimensions do not match")));
}
public void testParsePolyWithStoredZ() throws IOException {
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 0, 0));
shellCoordinates.add(new Coordinate(101, 0, 0));
shellCoordinates.add(new Coordinate(101, 1, 0));
shellCoordinates.add(new Coordinate(100, 1, 5));
shellCoordinates.add(new Coordinate(100, 0, 5));
PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder().coordinates(shellCoordinates));
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().value(builder.toWKT());
XContentParser parser = createParser(xContentBuilder);
parser.nextToken();
Settings indexSettings = Settings.builder()
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_6_3_0)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext);
ShapeBuilder shapeBuilder = ShapeParser.parse(parser, mapperBuilder);
assertEquals(shapeBuilder.numDimensions(), 3);
}
public void testParseSelfCrossingPolygon() throws IOException {
@ -235,7 +347,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
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);
assertMalformed(builder);
}
public void testInvalidGeometryType() throws IOException {

View File

@ -653,4 +653,49 @@ public class ShapeBuilderTests extends ESTestCase {
Exception e = expectThrows(InvalidShapeException.class, () -> builder.close().build());
assertThat(e.getMessage(), containsString("duplicate consecutive coordinates at: ("));
}
public void testPolygon3D() {
String expected = "{\n" +
" \"type\" : \"polygon\",\n" +
" \"orientation\" : \"right\",\n" +
" \"coordinates\" : [\n" +
" [\n" +
" [\n" +
" -45.0,\n" +
" 30.0,\n" +
" 100.0\n" +
" ],\n" +
" [\n" +
" 45.0,\n" +
" 30.0,\n" +
" 75.0\n" +
" ],\n" +
" [\n" +
" 45.0,\n" +
" -30.0,\n" +
" 77.0\n" +
" ],\n" +
" [\n" +
" -45.0,\n" +
" -30.0,\n" +
" 101.0\n" +
" ],\n" +
" [\n" +
" -45.0,\n" +
" 30.0,\n" +
" 110.0\n" +
" ]\n" +
" ]\n" +
" ]\n" +
"}";
PolygonBuilder pb = new PolygonBuilder(new CoordinatesBuilder()
.coordinate(new Coordinate(-45, 30, 100))
.coordinate(new Coordinate(45, 30, 75))
.coordinate(new Coordinate(45, -30, 77))
.coordinate(new Coordinate(-45, -30, 101))
.coordinate(new Coordinate(-45, 30, 110)));
assertEquals(expected, pb.toString());
}
}

View File

@ -34,14 +34,17 @@ import org.elasticsearch.test.InternalSettingsPlugin;
import org.elasticsearch.test.geo.RandomGeoGenerator;
import org.hamcrest.CoreMatchers;
import java.io.IOException;
import java.util.Collection;
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.elasticsearch.common.geo.GeoHashUtils.stringEncode;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
public class GeoPointFieldMapperTests extends ESSingleNodeTestCase {
@ -121,6 +124,43 @@ public class GeoPointFieldMapperTests extends ESSingleNodeTestCase {
assertThat(doc.rootDoc().getField("point"), notNullValue());
}
public void testLatLonStringWithZValue() throws Exception {
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("properties").startObject("point").field("type", "geo_point")
.field(IGNORE_Z_VALUE.getPreferredName(), true);
String mapping = Strings.toString(xContentBuilder.endObject().endObject().endObject().endObject());
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type",
new CompressedXContent(mapping));
ParsedDocument doc = defaultMapper.parse(SourceToParse.source("test", "type", "1", BytesReference
.bytes(XContentFactory.jsonBuilder()
.startObject()
.field("point", "1.2,1.3,10.0")
.endObject()),
XContentType.JSON));
assertThat(doc.rootDoc().getField("point"), notNullValue());
}
public void testLatLonStringWithZValueException() throws Exception {
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("properties").startObject("point").field("type", "geo_point")
.field(IGNORE_Z_VALUE.getPreferredName(), false);
String mapping = Strings.toString(xContentBuilder.endObject().endObject().endObject().endObject());
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type",
new CompressedXContent(mapping));
SourceToParse source = SourceToParse.source("test", "type", "1", BytesReference
.bytes(XContentFactory.jsonBuilder()
.startObject()
.field("point", "1.2,1.3,10.0")
.endObject()),
XContentType.JSON);
Exception e = expectThrows(MapperParsingException.class, () -> defaultMapper.parse(source));
assertThat(e.getCause().getMessage(), containsString("but [ignore_z_value] parameter is [false]"));
}
public void testLatLonInOneValueStored() throws Exception {
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("properties").startObject("point").field("type", "geo_point");
@ -230,6 +270,41 @@ public class GeoPointFieldMapperTests extends ESSingleNodeTestCase {
assertThat(doc.rootDoc().getFields("point").length, CoreMatchers.equalTo(4));
}
/**
* Test that accept_z_value parameter correctly parses
*/
public void testIgnoreZValue() throws IOException {
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("properties").startObject("location")
.field("type", "geo_point")
.field(IGNORE_Z_VALUE.getPreferredName(), "true")
.endObject().endObject()
.endObject().endObject());
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
.parse("type1", new CompressedXContent(mapping));
FieldMapper fieldMapper = defaultMapper.mappers().getMapper("location");
assertThat(fieldMapper, instanceOf(GeoPointFieldMapper.class));
boolean ignoreZValue = ((GeoPointFieldMapper)fieldMapper).ignoreZValue().value();
assertThat(ignoreZValue, equalTo(true));
// explicit false accept_z_value test
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("properties").startObject("location")
.field("type", "geo_point")
.field(IGNORE_Z_VALUE.getPreferredName(), "false")
.endObject().endObject()
.endObject().endObject());
defaultMapper = createIndex("test2").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping));
fieldMapper = defaultMapper.mappers().getMapper("location");
assertThat(fieldMapper, instanceOf(GeoPointFieldMapper.class));
ignoreZValue = ((GeoPointFieldMapper)fieldMapper).ignoreZValue().value();
assertThat(ignoreZValue, equalTo(false));
}
public void testMultiField() throws Exception {
int numDocs = randomIntBetween(10, 100);
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("pin").startObject("properties").startObject("location")

View File

@ -35,6 +35,7 @@ import org.elasticsearch.test.InternalSettingsPlugin;
import java.io.IOException;
import java.util.Collection;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
@ -138,6 +139,42 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
assertThat(coerce, equalTo(false));
}
/**
* Test that accept_z_value parameter correctly parses
*/
public void testIgnoreZValue() throws IOException {
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("properties").startObject("location")
.field("type", "geo_shape")
.field(IGNORE_Z_VALUE.getPreferredName(), "true")
.endObject().endObject()
.endObject().endObject());
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
.parse("type1", new CompressedXContent(mapping));
FieldMapper fieldMapper = defaultMapper.mappers().getMapper("location");
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
boolean ignoreZValue = ((GeoShapeFieldMapper)fieldMapper).ignoreZValue().value();
assertThat(ignoreZValue, equalTo(true));
// explicit false accept_z_value test
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("properties").startObject("location")
.field("type", "geo_shape")
.field(IGNORE_Z_VALUE.getPreferredName(), "false")
.endObject().endObject()
.endObject().endObject());
defaultMapper = createIndex("test2").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping));
fieldMapper = defaultMapper.mappers().getMapper("location");
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
ignoreZValue = ((GeoShapeFieldMapper)fieldMapper).ignoreZValue().value();
assertThat(ignoreZValue, equalTo(false));
}
/**
* Test that ignore_malformed parameter correctly parses
*/

View File

@ -410,6 +410,19 @@ public class GeoUtilsTests extends ESTestCase {
}
}
public void testParseGeoPointStringZValueError() throws IOException {
double lat = randomDouble() * 180 - 90 + randomIntBetween(-1000, 1000) * 180;
double lon = randomDouble() * 360 - 180 + randomIntBetween(-1000, 1000) * 360;
double alt = randomDouble() * 1000;
XContentBuilder json = jsonBuilder().startObject().field("foo", lat + "," + lon + "," + alt).endObject();
XContentParser parser = createParser(json);
while (parser.currentToken() != Token.VALUE_STRING) {
parser.nextToken();
}
Exception e = expectThrows(ElasticsearchParseException.class, () -> GeoUtils.parseGeoPoint(parser, new GeoPoint(), false));
assertThat(e.getMessage(), containsString("but [ignore_z_value] parameter is [false]"));
}
public void testParseGeoPointGeohash() throws IOException {
for (int i = 0; i < 100; i++) {
int geoHashLength = randomIntBetween(1, GeoHashUtils.PRECISION);
@ -509,7 +522,21 @@ public class GeoUtilsTests extends ESTestCase {
parser.nextToken();
}
Exception e = expectThrows(ElasticsearchParseException.class, () -> GeoUtils.parseGeoPoint(parser));
assertThat(e.getMessage(), is("only two values allowed"));
assertThat(e.getMessage(), is("Exception parsing coordinates: found Z value [0.0] but [ignore_z_value] parameter is [false]"));
}
public void testParseGeoPointArray3D() throws IOException {
double lat = 90.0;
double lon = -180.0;
double elev = 0.0;
XContentBuilder json = jsonBuilder().startObject().startArray("foo").value(lon).value(lat).value(elev).endArray().endObject();
XContentParser parser = createParser(json);
while (parser.currentToken() != Token.START_ARRAY) {
parser.nextToken();
}
GeoPoint point = GeoUtils.parseGeoPoint(parser, new GeoPoint(), true);
assertThat(point.lat(), equalTo(lat));
assertThat(point.lon(), equalTo(lon));
}
public void testParseGeoPointArrayWrongType() throws IOException {