فهرست منبع

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
Nicholas Knize 8 سال پیش
والد
کامیت
fede633563
29فایلهای تغییر یافته به همراه739 افزوده شده و 114 حذف شده
  1. 7 0
      docs/reference/mapping/types/geo-point.asciidoc
  2. 6 0
      docs/reference/mapping/types/geo-shape.asciidoc
  3. 28 8
      server/src/main/java/org/elasticsearch/common/geo/GeoPoint.java
  4. 10 16
      server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java
  5. 4 0
      server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java
  6. 11 1
      server/src/main/java/org/elasticsearch/common/geo/builders/CoordinatesBuilder.java
  7. 8 0
      server/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java
  8. 9 0
      server/src/main/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilder.java
  9. 9 0
      server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java
  10. 8 0
      server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java
  11. 9 0
      server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java
  12. 9 0
      server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java
  13. 5 0
      server/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java
  14. 9 0
      server/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java
  15. 19 2
      server/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java
  16. 11 0
      server/src/main/java/org/elasticsearch/common/geo/parsers/CoordinateNode.java
  17. 26 9
      server/src/main/java/org/elasticsearch/common/geo/parsers/GeoJsonParser.java
  18. 51 36
      server/src/main/java/org/elasticsearch/common/geo/parsers/GeoWKTParser.java
  19. 2 1
      server/src/main/java/org/elasticsearch/common/geo/parsers/ShapeParser.java
  20. 46 15
      server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java
  21. 34 3
      server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java
  22. 1 1
      server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java
  23. 2 2
      server/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java
  24. 108 9
      server/src/test/java/org/elasticsearch/common/geo/GeoJsonShapeParserTests.java
  25. 122 10
      server/src/test/java/org/elasticsearch/common/geo/GeoWKTShapeParserTests.java
  26. 45 0
      server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java
  27. 75 0
      server/src/test/java/org/elasticsearch/index/mapper/GeoPointFieldMapperTests.java
  28. 37 0
      server/src/test/java/org/elasticsearch/index/mapper/GeoShapeFieldMapperTests.java
  29. 28 1
      server/src/test/java/org/elasticsearch/index/search/geo/GeoUtilsTests.java

+ 7 - 0
docs/reference/mapping/types/geo-point.asciidoc

@@ -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

+ 6 - 0
docs/reference/mapping/types/geo-shape.asciidoc

@@ -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`
+
 
 |=======================================================================
 

+ 28 - 8
server/src/main/java/org/elasticsearch/common/geo/GeoPoint.java

@@ -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;
+    }
 }

+ 10 - 16
server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java

@@ -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) {

+ 4 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java

@@ -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());

+ 11 - 1
server/src/main/java/org/elasticsearch/common/geo/builders/CoordinatesBuilder.java

@@ -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;
     }
 

+ 8 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java

@@ -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);

+ 9 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilder.java

@@ -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() {
 

+ 9 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java

@@ -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()]);

+ 8 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java

@@ -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();

+ 9 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java

@@ -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;
+    }
 }

+ 9 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java

@@ -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() {
 

+ 5 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java

@@ -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;
+    }
 }

+ 9 - 0
server/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java

@@ -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;

+ 19 - 2
server/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java

@@ -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();
     }
 
     /**

+ 11 - 0
server/src/main/java/org/elasticsearch/common/geo/parsers/CoordinateNode.java

@@ -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) {

+ 26 - 9
server/src/main/java/org/elasticsearch/common/geo/parsers/GeoJsonParser.java

@@ -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();
+        }
+        // do not support > 3 dimensions
+        if (parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
+            throw new ElasticsearchParseException("geo coordinates greater than 3 dimensions are not supported");
         }
-        // todo support z/alt
-        return new Coordinate(lon, lat);
+        return new Coordinate(lon, lat, alt);
     }
 
     /**

+ 51 - 36
server/src/main/java/org/elasticsearch/common/geo/parsers/GeoWKTParser.java

@@ -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);
     }
 
-    /** throws an exception if the parsed geometry type does not match the expected shape type */
     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,
+                                                 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;
     }

+ 2 - 1
server/src/main/java/org/elasticsearch/common/geo/parsers/ShapeParser.java

@@ -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");
     }

+ 46 - 15
server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java

@@ -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;
     }
 }

+ 34 - 3
server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java

@@ -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;

+ 1 - 1
server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java

@@ -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

+ 2 - 2
server/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java

@@ -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) {

+ 108 - 9
server/src/test/java/org/elasticsearch/common/geo/GeoJsonShapeParserTests.java

@@ -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")

+ 122 - 10
server/src/test/java/org/elasticsearch/common/geo/GeoWKTShapeParserTests.java

@@ -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 {

+ 45 - 0
server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java

@@ -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());
+    }
 }

+ 75 - 0
server/src/test/java/org/elasticsearch/index/mapper/GeoPointFieldMapperTests.java

@@ -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")

+ 37 - 0
server/src/test/java/org/elasticsearch/index/mapper/GeoShapeFieldMapperTests.java

@@ -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
      */

+ 28 - 1
server/src/test/java/org/elasticsearch/index/search/geo/GeoUtilsTests.java

@@ -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 {