mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-23 05:15:04 +00:00
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:
parent
794de63232
commit
fede633563
@ -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
|
||||
|
@ -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`
|
||||
|
||||
|
||||
|=======================================================================
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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()]);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -133,7 +133,7 @@ public class GeoContextMapping extends ContextMapping<GeoQueryContext> {
|
||||
* <li>String/Object/Array: <pre>"GEO POINT"</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) {
|
||||
|
@ -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")
|
||||
|
@ -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 {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user