Skip to content

Commit 6bd1853

Browse files
authored
Geo: add validator that only checks altitude (#43893)
By default, we don't check ranges while indexing geo_shapes. As a result, it is possible to index geoshapes that contain contain coordinates outside of -90 +90 and -180 +180 ranges. Such geoshapes will currently break SQL and ML retrieval mechanism. This commit removes these restriction from the validator is used in SQL and ML retrieval.
1 parent 4ad081b commit 6bd1853

File tree

18 files changed

+248
-11
lines changed

18 files changed

+248
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.geo.utils;
21+
22+
import org.elasticsearch.geo.geometry.Circle;
23+
import org.elasticsearch.geo.geometry.Geometry;
24+
import org.elasticsearch.geo.geometry.GeometryCollection;
25+
import org.elasticsearch.geo.geometry.GeometryVisitor;
26+
import org.elasticsearch.geo.geometry.Line;
27+
import org.elasticsearch.geo.geometry.LinearRing;
28+
import org.elasticsearch.geo.geometry.MultiLine;
29+
import org.elasticsearch.geo.geometry.MultiPoint;
30+
import org.elasticsearch.geo.geometry.MultiPolygon;
31+
import org.elasticsearch.geo.geometry.Point;
32+
import org.elasticsearch.geo.geometry.Polygon;
33+
import org.elasticsearch.geo.geometry.Rectangle;
34+
35+
/**
36+
* Validator that only checks that altitude only shows up if ignoreZValue is set to true.
37+
*/
38+
public class StandardValidator implements GeometryValidator {
39+
40+
private final boolean ignoreZValue;
41+
42+
public StandardValidator(boolean ignoreZValue) {
43+
this.ignoreZValue = ignoreZValue;
44+
}
45+
46+
protected void checkAltitude(double zValue) {
47+
if (ignoreZValue == false && Double.isNaN(zValue) == false) {
48+
throw new IllegalArgumentException("found Z value [" + zValue + "] but [ignore_z_value] "
49+
+ "parameter is [" + ignoreZValue + "]");
50+
}
51+
}
52+
53+
@Override
54+
public void validate(Geometry geometry) {
55+
if (ignoreZValue == false) {
56+
geometry.visit(new GeometryVisitor<Void, RuntimeException>() {
57+
58+
@Override
59+
public Void visit(Circle circle) throws RuntimeException {
60+
checkAltitude(circle.getAlt());
61+
return null;
62+
}
63+
64+
@Override
65+
public Void visit(GeometryCollection<?> collection) throws RuntimeException {
66+
for (Geometry g : collection) {
67+
g.visit(this);
68+
}
69+
return null;
70+
}
71+
72+
@Override
73+
public Void visit(Line line) throws RuntimeException {
74+
for (int i = 0; i < line.length(); i++) {
75+
checkAltitude(line.getAlt(i));
76+
}
77+
return null;
78+
}
79+
80+
@Override
81+
public Void visit(LinearRing ring) throws RuntimeException {
82+
for (int i = 0; i < ring.length(); i++) {
83+
checkAltitude(ring.getAlt(i));
84+
}
85+
return null;
86+
}
87+
88+
@Override
89+
public Void visit(MultiLine multiLine) throws RuntimeException {
90+
return visit((GeometryCollection<?>) multiLine);
91+
}
92+
93+
@Override
94+
public Void visit(MultiPoint multiPoint) throws RuntimeException {
95+
return visit((GeometryCollection<?>) multiPoint);
96+
}
97+
98+
@Override
99+
public Void visit(MultiPolygon multiPolygon) throws RuntimeException {
100+
return visit((GeometryCollection<?>) multiPolygon);
101+
}
102+
103+
@Override
104+
public Void visit(Point point) throws RuntimeException {
105+
checkAltitude(point.getAlt());
106+
return null;
107+
}
108+
109+
@Override
110+
public Void visit(Polygon polygon) throws RuntimeException {
111+
polygon.getPolygon().visit(this);
112+
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
113+
polygon.getHole(i).visit(this);
114+
}
115+
return null;
116+
}
117+
118+
@Override
119+
public Void visit(Rectangle rectangle) throws RuntimeException {
120+
checkAltitude(rectangle.getMinAlt());
121+
checkAltitude(rectangle.getMaxAlt());
122+
return null;
123+
}
124+
});
125+
}
126+
}
127+
}
128+

libs/geo/src/test/java/org/elasticsearch/geo/geometry/CircleTests.java

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
2323
import org.elasticsearch.geo.utils.GeometryValidator;
24+
import org.elasticsearch.geo.utils.StandardValidator;
2425
import org.elasticsearch.geo.utils.WellKnownText;
2526

2627
import java.io.IOException;
@@ -59,5 +60,10 @@ public void testInitValidation() {
5960

6061
ex = expectThrows(IllegalArgumentException.class, () -> validator.validate(new Circle(10, 200, 1)));
6162
assertEquals("invalid longitude 200.0; must be between -180.0 and 180.0", ex.getMessage());
63+
64+
ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(new Circle(10, 200, 1, 20)));
65+
assertEquals("found Z value [1.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
66+
67+
new StandardValidator(true).validate(new Circle(10, 200, 1, 20));
6268
}
6369
}

libs/geo/src/test/java/org/elasticsearch/geo/geometry/GeometryCollectionTests.java

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.geo.geometry;
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
23+
import org.elasticsearch.geo.utils.StandardValidator;
2324
import org.elasticsearch.geo.utils.WellKnownText;
2425

2526
import java.io.IOException;
@@ -58,5 +59,11 @@ public void testInitValidation() {
5859
ex = expectThrows(IllegalArgumentException.class, () -> new GeometryCollection<>(
5960
Arrays.asList(new Point(10, 20), new Point(10, 20, 30))));
6061
assertEquals("all elements of the collection should have the same number of dimension", ex.getMessage());
62+
63+
ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(
64+
new GeometryCollection<Geometry>(Collections.singletonList(new Point(10, 20, 30)))));
65+
assertEquals("found Z value [30.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
66+
67+
new StandardValidator(true).validate(new GeometryCollection<Geometry>(Collections.singletonList(new Point(10, 20, 30))));
6168
}
6269
}

libs/geo/src/test/java/org/elasticsearch/geo/geometry/LineTests.java

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
2323
import org.elasticsearch.geo.utils.GeometryValidator;
24+
import org.elasticsearch.geo.utils.StandardValidator;
2425
import org.elasticsearch.geo.utils.WellKnownText;
2526

2627
import java.io.IOException;
@@ -59,6 +60,12 @@ public void testInitValidation() {
5960
ex = expectThrows(IllegalArgumentException.class,
6061
() -> validator.validate(new Line(new double[]{1, 100, 3, 1}, new double[]{3, 4, 5, 3})));
6162
assertEquals("invalid latitude 100.0; must be between -90.0 and 90.0", ex.getMessage());
63+
64+
ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(
65+
new Line(new double[]{1, 2}, new double[]{3, 4}, new double[]{6, 5})));
66+
assertEquals("found Z value [6.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
67+
68+
new StandardValidator(true).validate(new Line(new double[]{1, 2}, new double[]{3, 4}, new double[]{6, 5}));
6269
}
6370

6471
public void testWKTValidation() {

libs/geo/src/test/java/org/elasticsearch/geo/geometry/LinearRingTests.java

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
2323
import org.elasticsearch.geo.utils.GeometryValidator;
24+
import org.elasticsearch.geo.utils.StandardValidator;
2425
import org.elasticsearch.geo.utils.WellKnownText;
2526
import org.elasticsearch.test.ESTestCase;
2627

@@ -58,6 +59,12 @@ public void testInitValidation() {
5859
ex = expectThrows(IllegalArgumentException.class,
5960
() -> validator.validate(new LinearRing(new double[]{1, 100, 3, 1}, new double[]{3, 4, 5, 3})));
6061
assertEquals("invalid latitude 100.0; must be between -90.0 and 90.0", ex.getMessage());
62+
63+
ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(
64+
new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{1, 1, 1, 1})));
65+
assertEquals("found Z value [1.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
66+
67+
new StandardValidator(true).validate(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{1, 1, 1, 1}));
6168
}
6269

6370
public void testVisitor() {

libs/geo/src/test/java/org/elasticsearch/geo/geometry/MultiLineTests.java

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.geo.geometry;
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
23+
import org.elasticsearch.geo.utils.StandardValidator;
2324
import org.elasticsearch.geo.utils.WellKnownText;
2425

2526
import java.io.IOException;
@@ -50,4 +51,13 @@ public void testBasicSerialization() throws IOException, ParseException {
5051
assertEquals("multilinestring EMPTY", wkt.toWKT(MultiLine.EMPTY));
5152
assertEquals(MultiLine.EMPTY, wkt.fromWKT("multilinestring EMPTY)"));
5253
}
54+
55+
public void testValidation() {
56+
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(
57+
new MultiLine(Collections.singletonList(new Line(new double[]{1, 2}, new double[]{3, 4}, new double[]{6, 5})))));
58+
assertEquals("found Z value [6.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
59+
60+
new StandardValidator(true).validate(
61+
new MultiLine(Collections.singletonList(new Line(new double[]{1, 2}, new double[]{3, 4}, new double[]{6, 5}))));
62+
}
5363
}

libs/geo/src/test/java/org/elasticsearch/geo/geometry/MultiPointTests.java

+9
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.geo.geometry;
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
23+
import org.elasticsearch.geo.utils.StandardValidator;
2324
import org.elasticsearch.geo.utils.WellKnownText;
2425

2526
import java.io.IOException;
@@ -61,4 +62,12 @@ public void testBasicSerialization() throws IOException, ParseException {
6162
assertEquals("multipoint EMPTY", wkt.toWKT(MultiPoint.EMPTY));
6263
assertEquals(MultiPoint.EMPTY, wkt.fromWKT("multipoint EMPTY)"));
6364
}
65+
66+
public void testValidation() {
67+
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(
68+
new MultiPoint(Collections.singletonList(new Point(1, 2 ,3)))));
69+
assertEquals("found Z value [3.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
70+
71+
new StandardValidator(true).validate(new MultiPoint(Collections.singletonList(new Point(1, 2 ,3))));
72+
}
6473
}

libs/geo/src/test/java/org/elasticsearch/geo/geometry/MultiPolygonTests.java

+13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.geo.geometry;
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
23+
import org.elasticsearch.geo.utils.StandardValidator;
2324
import org.elasticsearch.geo.utils.WellKnownText;
2425

2526
import java.io.IOException;
@@ -52,4 +53,16 @@ public void testBasicSerialization() throws IOException, ParseException {
5253
assertEquals("multipolygon EMPTY", wkt.toWKT(MultiPolygon.EMPTY));
5354
assertEquals(MultiPolygon.EMPTY, wkt.fromWKT("multipolygon EMPTY)"));
5455
}
56+
57+
public void testValidation() {
58+
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(
59+
new MultiPolygon(Collections.singletonList(
60+
new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{1, 2, 3, 1}))
61+
))));
62+
assertEquals("found Z value [1.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
63+
64+
new StandardValidator(true).validate(
65+
new MultiPolygon(Collections.singletonList(
66+
new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{1, 2, 3, 1})))));
67+
}
5568
}

libs/geo/src/test/java/org/elasticsearch/geo/geometry/PointTests.java

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
2323
import org.elasticsearch.geo.utils.GeometryValidator;
24+
import org.elasticsearch.geo.utils.StandardValidator;
2425
import org.elasticsearch.geo.utils.WellKnownText;
2526

2627
import java.io.IOException;
@@ -51,6 +52,11 @@ public void testInitValidation() {
5152

5253
ex = expectThrows(IllegalArgumentException.class, () -> validator.validate(new Point(10, 500)));
5354
assertEquals("invalid longitude 500.0; must be between -180.0 and 180.0", ex.getMessage());
55+
56+
ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(new Point(1, 2, 3)));
57+
assertEquals("found Z value [3.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
58+
59+
new StandardValidator(true).validate(new Point(1, 2, 3));
5460
}
5561

5662
public void testWKTValidation() {

libs/geo/src/test/java/org/elasticsearch/geo/geometry/PolygonTests.java

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.geo.geometry;
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
23+
import org.elasticsearch.geo.utils.StandardValidator;
2324
import org.elasticsearch.geo.utils.WellKnownText;
2425

2526
import java.io.IOException;
@@ -70,6 +71,13 @@ public void testInitValidation() {
7071
() -> new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{5, 4, 3, 5}),
7172
Collections.singletonList(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}))));
7273
assertEquals("holes must have the same number of dimensions as the polygon", ex.getMessage());
74+
75+
ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(
76+
new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{1, 2, 3, 1}))));
77+
assertEquals("found Z value [1.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
78+
79+
new StandardValidator(true).validate(
80+
new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{1, 2, 3, 1})));
7381
}
7482

7583
public void testWKTValidation() {

libs/geo/src/test/java/org/elasticsearch/geo/geometry/RectangleTests.java

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.elasticsearch.geo.utils.GeographyValidator;
2323
import org.elasticsearch.geo.utils.GeometryValidator;
24+
import org.elasticsearch.geo.utils.StandardValidator;
2425
import org.elasticsearch.geo.utils.WellKnownText;
2526

2627
import java.io.IOException;
@@ -59,5 +60,11 @@ public void testInitValidation() {
5960
ex = expectThrows(IllegalArgumentException.class,
6061
() -> validator.validate(new Rectangle(1, 2, 2, 3, 5, Double.NaN)));
6162
assertEquals("only one altitude value is specified", ex.getMessage());
63+
64+
ex = expectThrows(IllegalArgumentException.class, () -> new StandardValidator(false).validate(
65+
new Rectangle(30, 40, 50, 10, 20, 60)));
66+
assertEquals("found Z value [20.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
67+
68+
new StandardValidator(true).validate(new Rectangle(30, 40, 50, 10, 20, 60));
6269
}
6370
}

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.elasticsearch.common.xcontent.XContentBuilder;
2525
import org.elasticsearch.common.xcontent.XContentParser;
2626
import org.elasticsearch.geo.geometry.Geometry;
27-
import org.elasticsearch.geo.utils.GeographyValidator;
27+
import org.elasticsearch.geo.utils.StandardValidator;
2828
import org.elasticsearch.geo.utils.GeometryValidator;
2929
import org.elasticsearch.geo.utils.WellKnownText;
3030

@@ -38,10 +38,9 @@ public final class GeometryParser {
3838

3939
private final GeoJson geoJsonParser;
4040
private final WellKnownText wellKnownTextParser;
41-
private final GeometryValidator validator;
4241

4342
public GeometryParser(boolean rightOrientation, boolean coerce, boolean ignoreZValue) {
44-
validator = new GeographyValidator(ignoreZValue);
43+
GeometryValidator validator = new StandardValidator(ignoreZValue);
4544
geoJsonParser = new GeoJson(rightOrientation, coerce, validator);
4645
wellKnownTextParser = new WellKnownText(coerce, validator);
4746
}

server/src/test/java/org/elasticsearch/common/geo/GeometryParserTests.java

+15
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.common.xcontent.XContentFactory;
2727
import org.elasticsearch.common.xcontent.XContentParseException;
2828
import org.elasticsearch.common.xcontent.XContentParser;
29+
import org.elasticsearch.geo.geometry.Line;
2930
import org.elasticsearch.geo.geometry.LinearRing;
3031
import org.elasticsearch.geo.geometry.Point;
3132
import org.elasticsearch.geo.geometry.Polygon;
@@ -114,6 +115,20 @@ public void testWKTParsing() throws Exception {
114115
newGeoJson.endObject();
115116
assertEquals("{\"val\":\"point (100.0 10.0)\"}", Strings.toString(newGeoJson));
116117
}
118+
119+
// Make sure we can parse values outside the normal lat lon boundaries
120+
XContentBuilder lineGeoJson = XContentFactory.jsonBuilder()
121+
.startObject()
122+
.field("foo", "LINESTRING (100 0, 200 10)")
123+
.endObject();
124+
125+
try (XContentParser parser = createParser(lineGeoJson)) {
126+
parser.nextToken(); // Start object
127+
parser.nextToken(); // Field Name
128+
parser.nextToken(); // Field Value
129+
assertEquals(new Line(new double[]{0, 10}, new double[]{100, 200} ),
130+
new GeometryParser(true, randomBoolean(), randomBoolean()).parse(parser));
131+
}
117132
}
118133

119134
public void testNullParsing() throws Exception {

0 commit comments

Comments
 (0)