Skip to content

Commit fede633

Browse files
committed
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
1 parent 794de63 commit fede633

29 files changed

+739
-114
lines changed

docs/reference/mapping/types/geo-point.asciidoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ The following parameters are accepted by `geo_point` fields:
105105
If `true`, malformed geo-points are ignored. If `false` (default),
106106
malformed geo-points throw an exception and reject the whole document.
107107

108+
<<ignore_z_value,`ignore_z_value`>>::
109+
110+
If `true` (default) three dimension points will be accepted (stored in source)
111+
but only latitude and longitude values will be indexed; the third dimension is
112+
ignored. If `false`, geo-points containing any more than latitude and longitude
113+
(two dimensions) values throw an exception and reject the whole document.
114+
108115
==== Using geo-points in scripts
109116

110117
When accessing the value of a geo-point in a script, the value is returned as

docs/reference/mapping/types/geo-shape.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ false (default), malformed GeoJSON and WKT shapes throw an exception and reject
9191
entire document.
9292
| `false`
9393

94+
|`ignore_z_value` |If `true` (default) three dimension points will be accepted (stored in source)
95+
but only latitude and longitude values will be indexed; the third dimension is ignored. If `false`,
96+
geo-points containing any more than latitude and longitude (two dimensions) values throw an exception
97+
and reject the whole document.
98+
| `true`
99+
94100

95101
|=======================================================================
96102

server/src/main/java/org/elasticsearch/common/geo/GeoPoint.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,17 @@
2525
import org.apache.lucene.index.IndexableField;
2626
import org.apache.lucene.util.BitUtil;
2727
import org.apache.lucene.util.BytesRef;
28-
import org.elasticsearch.common.xcontent.ToXContent;
2928
import org.elasticsearch.common.xcontent.ToXContentFragment;
3029
import org.elasticsearch.common.xcontent.XContentBuilder;
30+
import org.elasticsearch.ElasticsearchParseException;
31+
import org.elasticsearch.common.Strings;
3132

3233
import java.io.IOException;
3334
import java.util.Arrays;
3435

3536
import static org.elasticsearch.common.geo.GeoHashUtils.mortonEncode;
3637
import static org.elasticsearch.common.geo.GeoHashUtils.stringEncode;
38+
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
3739

3840
public final class GeoPoint implements ToXContentFragment {
3941

@@ -79,14 +81,24 @@ public GeoPoint resetLon(double lon) {
7981
}
8082

8183
public GeoPoint resetFromString(String value) {
82-
int comma = value.indexOf(',');
83-
if (comma != -1) {
84-
lat = Double.parseDouble(value.substring(0, comma).trim());
85-
lon = Double.parseDouble(value.substring(comma + 1).trim());
86-
} else {
87-
resetFromGeoHash(value);
84+
return resetFromString(value, false);
85+
}
86+
87+
public GeoPoint resetFromString(String value, final boolean ignoreZValue) {
88+
if (value.contains(",")) {
89+
String[] vals = value.split(",");
90+
if (vals.length > 3) {
91+
throw new ElasticsearchParseException("failed to parse [{}], expected 2 or 3 coordinates "
92+
+ "but found: [{}]", vals.length);
93+
}
94+
double lat = Double.parseDouble(vals[0].trim());
95+
double lon = Double.parseDouble(vals[1].trim());
96+
if (vals.length > 2) {
97+
GeoPoint.assertZValue(ignoreZValue, Double.parseDouble(vals[2].trim()));
98+
}
99+
return reset(lat, lon);
88100
}
89-
return this;
101+
return resetFromGeoHash(value);
90102
}
91103

92104
public GeoPoint resetFromIndexHash(long hash) {
@@ -193,4 +205,12 @@ public static GeoPoint fromGeohash(long geohashLong) {
193205
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
194206
return builder.latlon(lat, lon);
195207
}
208+
209+
public static double assertZValue(final boolean ignoreZValue, double zValue) {
210+
if (ignoreZValue == false) {
211+
throw new ElasticsearchParseException("Exception parsing coordinates: found Z value [{}] but [{}] "
212+
+ "parameter is [{}]", zValue, IGNORE_Z_VALUE, ignoreZValue);
213+
}
214+
return zValue;
215+
}
196216
}

server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
2525
import org.apache.lucene.util.SloppyMath;
2626
import org.elasticsearch.ElasticsearchParseException;
27+
import org.elasticsearch.common.Strings;
2728
import org.elasticsearch.common.unit.DistanceUnit;
2829
import org.elasticsearch.common.xcontent.XContentParser;
2930
import org.elasticsearch.common.xcontent.XContentParser.Token;
@@ -345,6 +346,11 @@ public static GeoPoint parseGeoPoint(XContentParser parser) throws IOException,
345346
return parseGeoPoint(parser, new GeoPoint());
346347
}
347348

349+
350+
public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point) throws IOException, ElasticsearchParseException {
351+
return parseGeoPoint(parser, point, false);
352+
}
353+
348354
/**
349355
* Parse a {@link GeoPoint} with a {@link XContentParser}. A geopoint has one of the following forms:
350356
*
@@ -359,7 +365,8 @@ public static GeoPoint parseGeoPoint(XContentParser parser) throws IOException,
359365
* @param point A {@link GeoPoint} that will be reset by the values parsed
360366
* @return new {@link GeoPoint} parsed from the parse
361367
*/
362-
public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point) throws IOException, ElasticsearchParseException {
368+
public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point, final boolean ignoreZValue)
369+
throws IOException, ElasticsearchParseException {
363370
double lat = Double.NaN;
364371
double lon = Double.NaN;
365372
String geohash = null;
@@ -438,33 +445,20 @@ public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point) thro
438445
} else if(element == 2) {
439446
lat = parser.doubleValue();
440447
} else {
441-
throw new ElasticsearchParseException("only two values allowed");
448+
GeoPoint.assertZValue(ignoreZValue, parser.doubleValue());
442449
}
443450
} else {
444451
throw new ElasticsearchParseException("numeric value expected");
445452
}
446453
}
447454
return point.reset(lat, lon);
448455
} else if(parser.currentToken() == Token.VALUE_STRING) {
449-
String data = parser.text();
450-
return parseGeoPoint(data, point);
456+
return point.resetFromString(parser.text(), ignoreZValue);
451457
} else {
452458
throw new ElasticsearchParseException("geo_point expected");
453459
}
454460
}
455461

456-
/** parse a {@link GeoPoint} from a String */
457-
public static GeoPoint parseGeoPoint(String data, GeoPoint point) {
458-
int comma = data.indexOf(',');
459-
if(comma > 0) {
460-
double lat = Double.parseDouble(data.substring(0, comma).trim());
461-
double lon = Double.parseDouble(data.substring(comma + 1).trim());
462-
return point.reset(lat, lon);
463-
} else {
464-
return point.resetFromGeoHash(data);
465-
}
466-
}
467-
468462
/** Returns the maximum distance/radius (in meters) from the point 'center' before overlapping */
469463
public static double maxRadialDistanceMeters(final double centerLat, final double centerLon) {
470464
if (Math.abs(centerLat) == MAX_LAT) {

server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ public String toWKT() {
173173
throw new UnsupportedOperationException("The WKT spec does not support CIRCLE geometry");
174174
}
175175

176+
public int numDimensions() {
177+
return Double.isNaN(center.z) ? 2 : 3;
178+
}
179+
176180
@Override
177181
public int hashCode() {
178182
return Objects.hash(center, radius, unit.ordinal());

server/src/main/java/org/elasticsearch/common/geo/builders/CoordinatesBuilder.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.common.geo.builders;
2121

2222
import com.vividsolutions.jts.geom.Coordinate;
23+
import org.elasticsearch.ElasticsearchException;
2324

2425
import java.util.ArrayList;
2526
import java.util.Arrays;
@@ -41,7 +42,16 @@ public class CoordinatesBuilder {
4142
* @return this
4243
*/
4344
public CoordinatesBuilder coordinate(Coordinate coordinate) {
44-
this.points.add(coordinate);
45+
int expectedDims;
46+
int actualDims;
47+
if (points.isEmpty() == false
48+
&& (expectedDims = Double.isNaN(points.get(0).z) ? 2 : 3) != (actualDims = Double.isNaN(coordinate.z) ? 2 : 3)) {
49+
throw new ElasticsearchException("unable to add coordinate to CoordinateBuilder: " +
50+
"coordinate dimensions do not match. Expected [{}] but found [{}]", expectedDims, actualDims);
51+
52+
} else {
53+
this.points.add(coordinate);
54+
}
4555
return this;
4656
}
4757

server/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public class EnvelopeBuilder extends ShapeBuilder<Rectangle, EnvelopeBuilder> {
4545
public EnvelopeBuilder(Coordinate topLeft, Coordinate bottomRight) {
4646
Objects.requireNonNull(topLeft, "topLeft of envelope cannot be null");
4747
Objects.requireNonNull(bottomRight, "bottomRight of envelope cannot be null");
48+
if (Double.isNaN(topLeft.z) != Double.isNaN(bottomRight.z)) {
49+
throw new IllegalArgumentException("expected same number of dimensions for topLeft and bottomRight");
50+
}
4851
this.topLeft = topLeft;
4952
this.bottomRight = bottomRight;
5053
}
@@ -114,6 +117,11 @@ public GeoShapeType type() {
114117
return TYPE;
115118
}
116119

120+
@Override
121+
public int numDimensions() {
122+
return Double.isNaN(topLeft.z) ? 2 : 3;
123+
}
124+
117125
@Override
118126
public int hashCode() {
119127
return Objects.hash(topLeft, bottomRight);

server/src/main/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,15 @@ public GeoShapeType type() {
159159
return TYPE;
160160
}
161161

162+
@Override
163+
public int numDimensions() {
164+
if (shapes == null || shapes.isEmpty()) {
165+
throw new IllegalStateException("unable to get number of dimensions, " +
166+
"GeometryCollection has not yet been initialized");
167+
}
168+
return shapes.get(0).numDimensions();
169+
}
170+
162171
@Override
163172
public Shape build() {
164173

server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ public GeoShapeType type() {
9191
return TYPE;
9292
}
9393

94+
@Override
95+
public int numDimensions() {
96+
if (coordinates == null || coordinates.isEmpty()) {
97+
throw new IllegalStateException("unable to get number of dimensions, " +
98+
"LineString has not yet been initialized");
99+
}
100+
return Double.isNaN(coordinates.get(0).z) ? 2 : 3;
101+
}
102+
94103
@Override
95104
public JtsGeometry build() {
96105
Coordinate[] coordinates = this.coordinates.toArray(new Coordinate[this.coordinates.size()]);

server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ protected StringBuilder contentToWKT() {
101101
return sb;
102102
}
103103

104+
public int numDimensions() {
105+
if (lines == null || lines.isEmpty()) {
106+
throw new IllegalStateException("unable to get number of dimensions, " +
107+
"LineStrings have not yet been initialized");
108+
}
109+
return lines.get(0).numDimensions();
110+
}
111+
104112
@Override
105113
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
106114
builder.startObject();

server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,13 @@ public XShapeCollection<Point> build() {
8080
public GeoShapeType type() {
8181
return TYPE;
8282
}
83+
84+
@Override
85+
public int numDimensions() {
86+
if (coordinates == null || coordinates.isEmpty()) {
87+
throw new IllegalStateException("unable to get number of dimensions, " +
88+
"LineString has not yet been initialized");
89+
}
90+
return Double.isNaN(coordinates.get(0).z) ? 2 : 3;
91+
}
8392
}

server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,15 @@ public GeoShapeType type() {
153153
return TYPE;
154154
}
155155

156+
@Override
157+
public int numDimensions() {
158+
if (polygons == null || polygons.isEmpty()) {
159+
throw new IllegalStateException("unable to get number of dimensions, " +
160+
"Polygons have not yet been initialized");
161+
}
162+
return polygons.get(0).numDimensions();
163+
}
164+
156165
@Override
157166
public Shape build() {
158167

server/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,9 @@ public Point build() {
9393
public GeoShapeType type() {
9494
return TYPE;
9595
}
96+
97+
@Override
98+
public int numDimensions() {
99+
return Double.isNaN(coordinates.get(0).z) ? 2 : 3;
100+
}
96101
}

server/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,15 @@ public GeoShapeType type() {
283283
return TYPE;
284284
}
285285

286+
@Override
287+
public int numDimensions() {
288+
if (shell == null) {
289+
throw new IllegalStateException("unable to get number of dimensions, " +
290+
"Polygon has not yet been initialized");
291+
}
292+
return shell.numDimensions();
293+
}
294+
286295
protected static Polygon polygon(GeometryFactory factory, Coordinate[][] polygon) {
287296
LinearRing shell = factory.createLinearRing(polygon[0]);
288297
LinearRing[] holes;

server/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.apache.logging.log4j.Logger;
2727
import org.elasticsearch.Assertions;
28+
import org.elasticsearch.Version;
2829
import org.elasticsearch.common.Strings;
2930
import org.elasticsearch.common.geo.GeoShapeType;
3031
import org.elasticsearch.common.geo.parsers.GeoWKTParser;
@@ -109,7 +110,13 @@ protected ShapeBuilder(StreamInput in) throws IOException {
109110
}
110111

111112
protected static Coordinate readFromStream(StreamInput in) throws IOException {
112-
return new Coordinate(in.readDouble(), in.readDouble());
113+
double x = in.readDouble();
114+
double y = in.readDouble();
115+
Double z = null;
116+
if (in.getVersion().onOrAfter(Version.V_6_3_0)) {
117+
z = in.readOptionalDouble();
118+
}
119+
return z == null ? new Coordinate(x, y) : new Coordinate(x, y, z);
113120
}
114121

115122
@Override
@@ -123,6 +130,9 @@ public void writeTo(StreamOutput out) throws IOException {
123130
protected static void writeCoordinateTo(Coordinate coordinate, StreamOutput out) throws IOException {
124131
out.writeDouble(coordinate.x);
125132
out.writeDouble(coordinate.y);
133+
if (out.getVersion().onOrAfter(Version.V_6_3_0)) {
134+
out.writeOptionalDouble(Double.isNaN(coordinate.z) ? null : coordinate.z);
135+
}
126136
}
127137

128138
@SuppressWarnings("unchecked")
@@ -217,6 +227,9 @@ protected static Coordinate shift(Coordinate coordinate, double dateline) {
217227
*/
218228
public abstract GeoShapeType type();
219229

230+
/** tracks number of dimensions for this shape */
231+
public abstract int numDimensions();
232+
220233
/**
221234
* Calculate the intersection of a line segment and a vertical dateline.
222235
*
@@ -429,7 +442,11 @@ protected static final boolean debugEnabled() {
429442
}
430443

431444
protected static XContentBuilder toXContent(XContentBuilder builder, Coordinate coordinate) throws IOException {
432-
return builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
445+
builder.startArray().value(coordinate.x).value(coordinate.y);
446+
if (Double.isNaN(coordinate.z) == false) {
447+
builder.value(coordinate.z);
448+
}
449+
return builder.endArray();
433450
}
434451

435452
/**

server/src/main/java/org/elasticsearch/common/geo/parsers/CoordinateNode.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.elasticsearch.common.geo.parsers;
2020

2121
import com.vividsolutions.jts.geom.Coordinate;
22+
import org.elasticsearch.ElasticsearchException;
2223
import org.elasticsearch.common.xcontent.ToXContentObject;
2324
import org.elasticsearch.common.xcontent.XContentBuilder;
2425

@@ -61,6 +62,16 @@ public boolean isEmpty() {
6162
return (coordinate == null && (children == null || children.isEmpty()));
6263
}
6364

65+
protected int numDimensions() {
66+
if (isEmpty()) {
67+
throw new ElasticsearchException("attempting to get number of dimensions on an empty coordinate node");
68+
}
69+
if (coordinate != null) {
70+
return Double.isNaN(coordinate.z) ? 2 : 3;
71+
}
72+
return children.get(0).numDimensions();
73+
}
74+
6475
@Override
6576
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
6677
if (children == null) {

0 commit comments

Comments
 (0)