Skip to content

Commit 23805fa

Browse files
authored
Geo: Fix Empty Geometry Collection Handling (#37978)
Fixes handling empty geometry collection and re-enables testParseGeometryCollection test. Fixes #37894
1 parent 53e80e9 commit 23805fa

File tree

7 files changed

+65
-20
lines changed

7 files changed

+65
-20
lines changed

libs/geo/src/main/java/org/elasticsearch/geo/utils/WellKnownText.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ public Void visit(MultiLine multiLine) {
121121

122122
@Override
123123
public Void visit(MultiPoint multiPoint) {
124+
if (multiPoint.isEmpty()) {
125+
sb.append(EMPTY);
126+
return null;
127+
}
124128
// walk through coordinates:
125129
sb.append(LPAREN);
126130
visitPoint(multiPoint.get(0).getLon(), multiPoint.get(0).getLat());

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.common.io.stream.StreamInput;
2828
import org.elasticsearch.common.io.stream.StreamOutput;
2929
import org.elasticsearch.common.xcontent.XContentBuilder;
30+
import org.elasticsearch.geo.geometry.GeometryCollection;
3031
import org.locationtech.spatial4j.shape.Shape;
3132

3233
import java.io.IOException;
@@ -186,6 +187,9 @@ public Shape buildS4J() {
186187

187188
@Override
188189
public org.elasticsearch.geo.geometry.GeometryCollection<org.elasticsearch.geo.geometry.Geometry> buildGeometry() {
190+
if (this.shapes.isEmpty()) {
191+
return GeometryCollection.EMPTY;
192+
}
189193
List<org.elasticsearch.geo.geometry.Geometry> shapes = new ArrayList<>(this.shapes.size());
190194

191195
for (ShapeBuilder shape : this.shapes) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ public JtsGeometry buildS4J() {
151151

152152
@Override
153153
public org.elasticsearch.geo.geometry.Geometry buildGeometry() {
154+
if (lines.isEmpty()) {
155+
return MultiLine.EMPTY;
156+
}
154157
if (wrapdateline) {
155158
List<org.elasticsearch.geo.geometry.Line> parts = new ArrayList<>();
156159
for (LineStringBuilder line : lines) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ public MultiPointBuilder(List<Coordinate> coordinates) {
4545
super(coordinates);
4646
}
4747

48+
/**
49+
* Creates a new empty MultiPoint builder
50+
*/
51+
public MultiPointBuilder() {
52+
super();
53+
}
54+
4855
/**
4956
* Read from a stream.
5057
*/
@@ -77,6 +84,9 @@ public XShapeCollection<Point> buildS4J() {
7784

7885
@Override
7986
public MultiPoint buildGeometry() {
87+
if (coordinates.isEmpty()) {
88+
return MultiPoint.EMPTY;
89+
}
8090
return new MultiPoint(coordinates.stream().map(coord -> new org.elasticsearch.geo.geometry.Point(coord.y, coord.x))
8191
.collect(Collectors.toList()));
8292
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ public MultiPolygon buildGeometry() {
198198
shapes.add((org.elasticsearch.geo.geometry.Polygon)poly);
199199
}
200200
}
201+
if (shapes.isEmpty()) {
202+
return MultiPolygon.EMPTY;
203+
}
201204
return new MultiPolygon(shapes);
202205
}
203206

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ private static MultiPointBuilder parseMultiPoint(StreamTokenizer stream, final b
198198
throws IOException, ElasticsearchParseException {
199199
String token = nextEmptyOrOpen(stream);
200200
if (token.equals(EMPTY)) {
201-
return null;
201+
return new MultiPointBuilder();
202202
}
203203
return new MultiPointBuilder(parseCoordinateList(stream, ignoreZValue, coerce));
204204
}
@@ -242,7 +242,7 @@ private static MultiLineStringBuilder parseMultiLine(StreamTokenizer stream, fin
242242
throws IOException, ElasticsearchParseException {
243243
String token = nextEmptyOrOpen(stream);
244244
if (token.equals(EMPTY)) {
245-
return null;
245+
return new MultiLineStringBuilder();
246246
}
247247
MultiLineStringBuilder builder = new MultiLineStringBuilder();
248248
builder.linestring(parseLine(stream, ignoreZValue, coerce));

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

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.common.xcontent.XContentBuilder;
4141
import org.elasticsearch.common.xcontent.XContentFactory;
4242
import org.elasticsearch.common.xcontent.XContentParser;
43+
import org.elasticsearch.geo.geometry.Geometry;
4344
import org.elasticsearch.geo.geometry.Line;
4445
import org.elasticsearch.geo.geometry.MultiLine;
4546
import org.elasticsearch.geo.geometry.MultiPoint;
@@ -112,27 +113,39 @@ public void testParsePoint() throws IOException {
112113

113114
@Override
114115
public void testParseMultiPoint() throws IOException {
115-
int numPoints = randomIntBetween(2, 100);
116+
int numPoints = randomIntBetween(0, 100);
116117
List<Coordinate> coordinates = new ArrayList<>(numPoints);
117118
for (int i = 0; i < numPoints; ++i) {
118119
coordinates.add(new Coordinate(GeoTestUtil.nextLongitude(), GeoTestUtil.nextLatitude()));
119120
}
120121

121-
Shape[] shapes = new Shape[numPoints];
122+
List<org.elasticsearch.geo.geometry.Point> points = new ArrayList<>(numPoints);
122123
for (int i = 0; i < numPoints; ++i) {
123124
Coordinate c = coordinates.get(i);
124-
shapes[i] = SPATIAL_CONTEXT.makePoint(c.x, c.y);
125+
points.add(new org.elasticsearch.geo.geometry.Point(c.y, c.x));
125126
}
126-
ShapeCollection<?> expected = shapeCollection(shapes);
127-
assertExpected(expected, new MultiPointBuilder(coordinates), true);
128127

129-
List<org.elasticsearch.geo.geometry.Point> points = new ArrayList<>(numPoints);
128+
Geometry expectedGeom;
129+
MultiPointBuilder actual;
130+
if (numPoints == 0) {
131+
expectedGeom = MultiPoint.EMPTY;
132+
actual = new MultiPointBuilder();
133+
} else {
134+
expectedGeom = new MultiPoint(points);
135+
actual = new MultiPointBuilder(coordinates);
136+
}
137+
138+
assertExpected(expectedGeom, actual, false);
139+
assertMalformed(actual);
140+
141+
assumeTrue("JTS test path cannot handle empty multipoints", numPoints > 1);
142+
Shape[] shapes = new Shape[numPoints];
130143
for (int i = 0; i < numPoints; ++i) {
131144
Coordinate c = coordinates.get(i);
132-
points.add(new org.elasticsearch.geo.geometry.Point(c.y, c.x));
145+
shapes[i] = SPATIAL_CONTEXT.makePoint(c.x, c.y);
133146
}
134-
assertExpected(new MultiPoint(points), new MultiPointBuilder(coordinates), false);
135-
assertMalformed(new MultiPointBuilder(coordinates));
147+
ShapeCollection<?> expected = shapeCollection(shapes);
148+
assertExpected(expected, new MultiPointBuilder(coordinates), true);
136149
}
137150

138151
private List<Coordinate> randomLineStringCoords() {
@@ -163,7 +176,7 @@ public void testParseLineString() throws IOException {
163176

164177
@Override
165178
public void testParseMultiLineString() throws IOException {
166-
int numLineStrings = randomIntBetween(2, 8);
179+
int numLineStrings = randomIntBetween(0, 8);
167180
List<LineString> lineStrings = new ArrayList<>(numLineStrings);
168181
MultiLineStringBuilder builder = new MultiLineStringBuilder();
169182
for (int j = 0; j < numLineStrings; ++j) {
@@ -173,18 +186,27 @@ public void testParseMultiLineString() throws IOException {
173186
builder.linestring(new LineStringBuilder(lsc));
174187
}
175188

176-
MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(
177-
lineStrings.toArray(new LineString[lineStrings.size()]));
178-
assertExpected(jtsGeom(expected), builder, true);
179-
180189
List<Line> lines = new ArrayList<>(lineStrings.size());
181190
for (int j = 0; j < lineStrings.size(); ++j) {
182191
Coordinate[] c = lineStrings.get(j).getCoordinates();
183192
lines.add(new Line(Arrays.stream(c).mapToDouble(i->i.y).toArray(),
184193
Arrays.stream(c).mapToDouble(i->i.x).toArray()));
185194
}
186-
assertExpected(new MultiLine(lines), builder, false);
195+
Geometry expectedGeom;
196+
if (lines.isEmpty()) {
197+
expectedGeom = MultiLine.EMPTY;
198+
} else if (lines.size() == 1) {
199+
expectedGeom = new Line(lines.get(0).getLats(), lines.get(0).getLons());
200+
} else {
201+
expectedGeom = new MultiLine(lines);
202+
}
203+
assertExpected(expectedGeom, builder, false);
187204
assertMalformed(builder);
205+
206+
MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(
207+
lineStrings.toArray(new LineString[lineStrings.size()]));
208+
assumeTrue("JTS test path cannot handle empty multilinestrings", numLineStrings > 1);
209+
assertExpected(jtsGeom(expected), builder, true);
188210
}
189211

190212
@Override
@@ -201,7 +223,7 @@ public void testParsePolygon() throws IOException {
201223

202224
@Override
203225
public void testParseMultiPolygon() throws IOException {
204-
int numPolys = randomIntBetween(2, 8);
226+
int numPolys = randomIntBetween(0, 8);
205227
MultiPolygonBuilder builder = new MultiPolygonBuilder();
206228
PolygonBuilder pb;
207229
Coordinate[] coordinates;
@@ -214,7 +236,7 @@ public void testParseMultiPolygon() throws IOException {
214236
shell = GEOMETRY_FACTORY.createLinearRing(coordinates);
215237
shapes[i] = GEOMETRY_FACTORY.createPolygon(shell, null);
216238
}
217-
239+
assumeTrue("JTS test path cannot handle empty multipolygon", numPolys > 1);
218240
Shape expected = shapeCollection(shapes);
219241
assertExpected(expected, builder, true);
220242
assertMalformed(builder);
@@ -429,7 +451,6 @@ public void testInvalidGeometryType() throws IOException {
429451
assertValidException(builder, IllegalArgumentException.class);
430452
}
431453

432-
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/37894")
433454
@Override
434455
public void testParseGeometryCollection() throws IOException {
435456
if (rarely()) {

0 commit comments

Comments
 (0)