Skip to content

Commit 00a1949

Browse files
authored
Streamline GeoJSON to map serialization (#60413) (#60429)
Optimizes GeoJSON to map serialization when retrieving spatial data through fields. Closes #60259
1 parent 8c22adc commit 00a1949

File tree

3 files changed

+164
-20
lines changed

3 files changed

+164
-20
lines changed

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

+139
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@
4848

4949
import java.io.IOException;
5050
import java.util.ArrayList;
51+
import java.util.HashMap;
5152
import java.util.List;
5253
import java.util.Locale;
54+
import java.util.Map;
5355

5456
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
5557
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
@@ -206,6 +208,143 @@ private XContentBuilder coordinatesToXContent(Polygon polygon) throws IOExceptio
206208
return builder.endObject();
207209
}
208210

211+
/**
212+
* Produces that same GeoJSON as toXContent only in parsed map form
213+
*/
214+
public static Map<String, Object> toMap(Geometry geometry) {
215+
Map<String, Object> root = new HashMap<>();
216+
root.put(FIELD_TYPE.getPreferredName(), getGeoJsonName(geometry));
217+
218+
geometry.visit(new GeometryVisitor<Void, RuntimeException>() {
219+
@Override
220+
public Void visit(Circle circle) {
221+
root.put(FIELD_RADIUS.getPreferredName(), DistanceUnit.METERS.toString(circle.getRadiusMeters()));
222+
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coordinatesToList(circle.getY(), circle.getX(), circle.getZ()));
223+
return null;
224+
}
225+
226+
@Override
227+
public Void visit(GeometryCollection<?> collection) {
228+
List<Object> geometries = new ArrayList<>(collection.size());
229+
230+
for (Geometry g : collection) {
231+
geometries.add(toMap(g));
232+
}
233+
root.put(FIELD_GEOMETRIES.getPreferredName(), geometries);
234+
return null;
235+
}
236+
237+
@Override
238+
public Void visit(Line line) {
239+
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coordinatesToList(line));
240+
return null;
241+
}
242+
243+
@Override
244+
public Void visit(LinearRing ring) {
245+
throw new UnsupportedOperationException("linearRing cannot be serialized using GeoJson");
246+
}
247+
248+
@Override
249+
public Void visit(MultiLine multiLine) {
250+
List<Object> lines = new ArrayList<>(multiLine.size());
251+
for (int i = 0; i < multiLine.size(); i++) {
252+
lines.add(coordinatesToList(multiLine.get(i)));
253+
}
254+
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), lines);
255+
return null;
256+
}
257+
258+
@Override
259+
public Void visit(MultiPoint multiPoint) {
260+
List<Object> points = new ArrayList<>(multiPoint.size());
261+
for (int i = 0; i < multiPoint.size(); i++) {
262+
Point p = multiPoint.get(i);
263+
List<Object> point = new ArrayList<>();
264+
point.add(p.getX());
265+
point.add(p.getY());
266+
if (p.hasZ()) {
267+
point.add(p.getZ());
268+
}
269+
points.add(point);
270+
}
271+
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), points);
272+
return null;
273+
}
274+
275+
@Override
276+
public Void visit(MultiPolygon multiPolygon) {
277+
List<Object> polygons = new ArrayList<>();
278+
for (int i = 0; i < multiPolygon.size(); i++) {
279+
polygons.add(coordinatesToList(multiPolygon.get(i)));
280+
}
281+
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), polygons);
282+
return null;
283+
}
284+
285+
@Override
286+
public Void visit(Point point) {
287+
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coordinatesToList(point.getY(), point.getX(), point.getZ()));
288+
return null;
289+
}
290+
291+
@Override
292+
public Void visit(Polygon polygon) {
293+
List<Object> coords = new ArrayList<>(polygon.getNumberOfHoles() + 1);
294+
coords.add(coordinatesToList(polygon.getPolygon()));
295+
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
296+
coords.add(coordinatesToList(polygon.getHole(i)));
297+
}
298+
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coords);
299+
return null;
300+
}
301+
302+
@Override
303+
public Void visit(Rectangle rectangle) {
304+
List<Object> coords = new ArrayList<>(2);
305+
coords.add(coordinatesToList(rectangle.getMaxY(), rectangle.getMinX(), rectangle.getMinZ())); // top left
306+
coords.add(coordinatesToList(rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMaxZ())); // bottom right
307+
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coords);
308+
return null;
309+
}
310+
311+
private List<Object> coordinatesToList(double lat, double lon, double alt) {
312+
List<Object> coords = new ArrayList<>(3);
313+
coords.add(lon);
314+
coords.add(lat);
315+
if (Double.isNaN(alt) == false) {
316+
coords.add(alt);
317+
}
318+
return coords;
319+
}
320+
321+
private List<Object> coordinatesToList(Line line) {
322+
List<Object> lines = new ArrayList<>(line.length());
323+
for (int i = 0; i < line.length(); i++) {
324+
List<Object> coords = new ArrayList<>(3);
325+
coords.add(line.getX(i));
326+
coords.add(line.getY(i));
327+
if (line.hasZ()) {
328+
coords.add(line.getZ(i));
329+
}
330+
lines.add(coords);
331+
}
332+
return lines;
333+
}
334+
335+
private List<Object> coordinatesToList(Polygon polygon) {
336+
List<Object> coords = new ArrayList<>(polygon.getNumberOfHoles() + 1);
337+
coords.add(coordinatesToList(polygon.getPolygon()));
338+
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
339+
coords.add(coordinatesToList(polygon.getHole(i)));
340+
}
341+
return coords;
342+
}
343+
344+
});
345+
return root;
346+
}
347+
209348
private static final ConstructingObjectParser<Geometry, GeoJson> PARSER =
210349
new ConstructingObjectParser<>("geojson", true, (a, c) -> {
211350
String type = (String) a[0];

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

+1-19
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,12 @@
1919

2020
package org.elasticsearch.common.geo;
2121

22-
import org.elasticsearch.common.bytes.BytesReference;
23-
import org.elasticsearch.common.io.stream.StreamInput;
24-
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
25-
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
2622
import org.elasticsearch.common.xcontent.ToXContent;
2723
import org.elasticsearch.common.xcontent.XContentBuilder;
28-
import org.elasticsearch.common.xcontent.XContentFactory;
2924
import org.elasticsearch.common.xcontent.XContentParser;
30-
import org.elasticsearch.common.xcontent.XContentType;
3125
import org.elasticsearch.geometry.Geometry;
3226

3327
import java.io.IOException;
34-
import java.io.UncheckedIOException;
3528

3629
public class GeoJsonGeometryFormat implements GeometryFormat<Geometry> {
3730
public static final String NAME = "geojson";
@@ -66,17 +59,6 @@ public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, To
6659

6760
@Override
6861
public Object toXContentAsObject(Geometry geometry) {
69-
try {
70-
XContentBuilder builder = XContentFactory.jsonBuilder();
71-
GeoJson.toXContent(geometry, builder, ToXContent.EMPTY_PARAMS);
72-
StreamInput input = BytesReference.bytes(builder).streamInput();
73-
74-
try (XContentParser parser = XContentType.JSON.xContent()
75-
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, input)) {
76-
return parser.map();
77-
}
78-
} catch (IOException e) {
79-
throw new UncheckedIOException(e);
80-
}
62+
return GeoJson.toMap(geometry);
8163
}
8264
}

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,24 @@
1919

2020
package org.elasticsearch.common.geo;
2121

22+
import org.elasticsearch.common.bytes.BytesReference;
23+
import org.elasticsearch.common.io.stream.StreamInput;
24+
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
25+
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
2226
import org.elasticsearch.common.xcontent.ToXContent;
2327
import org.elasticsearch.common.xcontent.ToXContentObject;
2428
import org.elasticsearch.common.xcontent.XContentBuilder;
29+
import org.elasticsearch.common.xcontent.XContentFactory;
2530
import org.elasticsearch.common.xcontent.XContentParser;
31+
import org.elasticsearch.common.xcontent.XContentType;
2632
import org.elasticsearch.geo.GeometryTestUtils;
2733
import org.elasticsearch.geometry.Geometry;
2834
import org.elasticsearch.geometry.utils.GeographyValidator;
2935
import org.elasticsearch.test.AbstractXContentTestCase;
3036
import org.elasticsearch.test.ESTestCase;
3137

3238
import java.io.IOException;
39+
import java.util.Map;
3340
import java.util.Objects;
3441
import java.util.function.Supplier;
3542

@@ -41,12 +48,13 @@
4148
import static org.elasticsearch.geo.GeometryTestUtils.randomMultiPolygon;
4249
import static org.elasticsearch.geo.GeometryTestUtils.randomPoint;
4350
import static org.elasticsearch.geo.GeometryTestUtils.randomPolygon;
51+
import static org.hamcrest.Matchers.equalTo;
4452

4553
public class GeoJsonSerializationTests extends ESTestCase {
4654

4755
private static class GeometryWrapper implements ToXContentObject {
4856

49-
private Geometry geometry;
57+
private final Geometry geometry;
5058
private static final GeoJson PARSER = new GeoJson(true, false, new GeographyValidator(true));
5159

5260
GeometryWrapper(Geometry geometry) {
@@ -126,4 +134,19 @@ public void testGeometryCollection() throws IOException {
126134
public void testCircle() throws IOException {
127135
xContentTest(() -> randomCircle(randomBoolean()));
128136
}
137+
138+
public void testToMap() throws IOException {
139+
for (int i = 0; i < 10; i++) {
140+
Geometry geometry = GeometryTestUtils.randomGeometry(randomBoolean());
141+
XContentBuilder builder = XContentFactory.jsonBuilder();
142+
GeoJson.toXContent(geometry, builder, ToXContent.EMPTY_PARAMS);
143+
StreamInput input = BytesReference.bytes(builder).streamInput();
144+
145+
try (XContentParser parser = XContentType.JSON.xContent()
146+
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, input)) {
147+
Map<String, Object> map = GeoJson.toMap(geometry);
148+
assertThat(parser.map(), equalTo(map));
149+
}
150+
}
151+
}
129152
}

0 commit comments

Comments
 (0)