Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Z value support to geo_point and geo_shape #25738

Merged
merged 1 commit into from Mar 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/reference/mapping/types/geo-point.asciidoc
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/mapping/types/geo-shape.asciidoc
Expand Up @@ -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`


|=======================================================================

Expand Down
36 changes: 28 additions & 8 deletions server/src/main/java/org/elasticsearch/common/geo/GeoPoint.java
Expand Up @@ -25,15 +25,17 @@
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 {

Expand Down Expand Up @@ -79,14 +81,24 @@ public GeoPoint resetLon(double lon) {
}

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(",");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please fail if vals.length > 3

Copy link

@hanoch hanoch Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jpountz @nknize Wouldn't it be better to be more lenient and accept the first 3 (or 2) dimensions, rather than completely fail? Adding failure now might break compatibility with previous behavior.

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) {
Expand Down Expand Up @@ -193,4 +205,12 @@ public static GeoPoint fromGeohash(long geohashLong) {
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;
}
}
26 changes: 10 additions & 16 deletions server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java
Expand Up @@ -24,6 +24,7 @@
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;
Expand Down Expand Up @@ -345,6 +346,11 @@ public static GeoPoint parseGeoPoint(XContentParser parser) throws IOException,
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:
*
Expand All @@ -359,7 +365,8 @@ public static GeoPoint parseGeoPoint(XContentParser parser) throws IOException,
* @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;
Expand Down Expand Up @@ -438,33 +445,20 @@ public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point) thro
} 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");
}
}
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) {
Expand Down
Expand Up @@ -173,6 +173,10 @@ public String toWKT() {
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());
Expand Down
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
Expand Up @@ -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;
}
Expand Down Expand Up @@ -114,6 +117,11 @@ public GeoShapeType type() {
return TYPE;
}

@Override
public int numDimensions() {
return Double.isNaN(topLeft.z) ? 2 : 3;
}

@Override
public int hashCode() {
return Objects.hash(topLeft, bottomRight);
Expand Down
Expand Up @@ -159,6 +159,15 @@ public GeoShapeType type() {
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() {

Expand Down
Expand Up @@ -91,6 +91,15 @@ 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;
}

@Override
public JtsGeometry build() {
Coordinate[] coordinates = this.coordinates.toArray(new Coordinate[this.coordinates.size()]);
Expand Down
Expand Up @@ -101,6 +101,14 @@ protected StringBuilder contentToWKT() {
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();
Expand Down
Expand Up @@ -80,4 +80,13 @@ public XShapeCollection<Point> build() {
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;
}
}
Expand Up @@ -153,6 +153,15 @@ public GeoShapeType type() {
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() {

Expand Down
Expand Up @@ -93,4 +93,9 @@ public Point build() {
public GeoShapeType type() {
return TYPE;
}

@Override
public int numDimensions() {
return Double.isNaN(coordinates.get(0).z) ? 2 : 3;
}
}
Expand Up @@ -283,6 +283,15 @@ public GeoShapeType type() {
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;
Expand Down
Expand Up @@ -25,6 +25,7 @@

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;
Expand Down Expand Up @@ -109,7 +110,13 @@ protected ShapeBuilder(StreamInput in) throws IOException {
}

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
Expand All @@ -123,6 +130,9 @@ public void writeTo(StreamOutput out) throws IOException {
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")
Expand Down Expand Up @@ -217,6 +227,9 @@ protected static Coordinate shift(Coordinate coordinate, double dateline) {
*/
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.
*
Expand Down Expand Up @@ -429,7 +442,11 @@ protected static final boolean debugEnabled() {
}

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

/**
Expand Down
Expand Up @@ -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;

Expand Down Expand Up @@ -61,6 +62,16 @@ public boolean isEmpty() {
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) {
Expand Down