Skip to content

Commit

Permalink
Streamline GeoJSON to map serialization (#60413) (#60429)
Browse files Browse the repository at this point in the history
Optimizes GeoJSON to map serialization when retrieving spatial data through
fields.

Closes #60259
  • Loading branch information
imotov committed Jul 29, 2020
1 parent 8c22adc commit 00a1949
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 20 deletions.
139 changes: 139 additions & 0 deletions server/src/main/java/org/elasticsearch/common/geo/GeoJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

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

/**
* Produces that same GeoJSON as toXContent only in parsed map form
*/
public static Map<String, Object> toMap(Geometry geometry) {
Map<String, Object> root = new HashMap<>();
root.put(FIELD_TYPE.getPreferredName(), getGeoJsonName(geometry));

geometry.visit(new GeometryVisitor<Void, RuntimeException>() {
@Override
public Void visit(Circle circle) {
root.put(FIELD_RADIUS.getPreferredName(), DistanceUnit.METERS.toString(circle.getRadiusMeters()));
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coordinatesToList(circle.getY(), circle.getX(), circle.getZ()));
return null;
}

@Override
public Void visit(GeometryCollection<?> collection) {
List<Object> geometries = new ArrayList<>(collection.size());

for (Geometry g : collection) {
geometries.add(toMap(g));
}
root.put(FIELD_GEOMETRIES.getPreferredName(), geometries);
return null;
}

@Override
public Void visit(Line line) {
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coordinatesToList(line));
return null;
}

@Override
public Void visit(LinearRing ring) {
throw new UnsupportedOperationException("linearRing cannot be serialized using GeoJson");
}

@Override
public Void visit(MultiLine multiLine) {
List<Object> lines = new ArrayList<>(multiLine.size());
for (int i = 0; i < multiLine.size(); i++) {
lines.add(coordinatesToList(multiLine.get(i)));
}
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), lines);
return null;
}

@Override
public Void visit(MultiPoint multiPoint) {
List<Object> points = new ArrayList<>(multiPoint.size());
for (int i = 0; i < multiPoint.size(); i++) {
Point p = multiPoint.get(i);
List<Object> point = new ArrayList<>();
point.add(p.getX());
point.add(p.getY());
if (p.hasZ()) {
point.add(p.getZ());
}
points.add(point);
}
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), points);
return null;
}

@Override
public Void visit(MultiPolygon multiPolygon) {
List<Object> polygons = new ArrayList<>();
for (int i = 0; i < multiPolygon.size(); i++) {
polygons.add(coordinatesToList(multiPolygon.get(i)));
}
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), polygons);
return null;
}

@Override
public Void visit(Point point) {
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coordinatesToList(point.getY(), point.getX(), point.getZ()));
return null;
}

@Override
public Void visit(Polygon polygon) {
List<Object> coords = new ArrayList<>(polygon.getNumberOfHoles() + 1);
coords.add(coordinatesToList(polygon.getPolygon()));
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
coords.add(coordinatesToList(polygon.getHole(i)));
}
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coords);
return null;
}

@Override
public Void visit(Rectangle rectangle) {
List<Object> coords = new ArrayList<>(2);
coords.add(coordinatesToList(rectangle.getMaxY(), rectangle.getMinX(), rectangle.getMinZ())); // top left
coords.add(coordinatesToList(rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMaxZ())); // bottom right
root.put(ShapeParser.FIELD_COORDINATES.getPreferredName(), coords);
return null;
}

private List<Object> coordinatesToList(double lat, double lon, double alt) {
List<Object> coords = new ArrayList<>(3);
coords.add(lon);
coords.add(lat);
if (Double.isNaN(alt) == false) {
coords.add(alt);
}
return coords;
}

private List<Object> coordinatesToList(Line line) {
List<Object> lines = new ArrayList<>(line.length());
for (int i = 0; i < line.length(); i++) {
List<Object> coords = new ArrayList<>(3);
coords.add(line.getX(i));
coords.add(line.getY(i));
if (line.hasZ()) {
coords.add(line.getZ(i));
}
lines.add(coords);
}
return lines;
}

private List<Object> coordinatesToList(Polygon polygon) {
List<Object> coords = new ArrayList<>(polygon.getNumberOfHoles() + 1);
coords.add(coordinatesToList(polygon.getPolygon()));
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
coords.add(coordinatesToList(polygon.getHole(i)));
}
return coords;
}

});
return root;
}

private static final ConstructingObjectParser<Geometry, GeoJson> PARSER =
new ConstructingObjectParser<>("geojson", true, (a, c) -> {
String type = (String) a[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,12 @@

package org.elasticsearch.common.geo;

import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.geometry.Geometry;

import java.io.IOException;
import java.io.UncheckedIOException;

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

@Override
public Object toXContentAsObject(Geometry geometry) {
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
GeoJson.toXContent(geometry, builder, ToXContent.EMPTY_PARAMS);
StreamInput input = BytesReference.bytes(builder).streamInput();

try (XContentParser parser = XContentType.JSON.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, input)) {
return parser.map();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return GeoJson.toMap(geometry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@

package org.elasticsearch.common.geo;

import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.utils.GeographyValidator;
import org.elasticsearch.test.AbstractXContentTestCase;
import org.elasticsearch.test.ESTestCase;

import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

Expand All @@ -41,12 +48,13 @@
import static org.elasticsearch.geo.GeometryTestUtils.randomMultiPolygon;
import static org.elasticsearch.geo.GeometryTestUtils.randomPoint;
import static org.elasticsearch.geo.GeometryTestUtils.randomPolygon;
import static org.hamcrest.Matchers.equalTo;

public class GeoJsonSerializationTests extends ESTestCase {

private static class GeometryWrapper implements ToXContentObject {

private Geometry geometry;
private final Geometry geometry;
private static final GeoJson PARSER = new GeoJson(true, false, new GeographyValidator(true));

GeometryWrapper(Geometry geometry) {
Expand Down Expand Up @@ -126,4 +134,19 @@ public void testGeometryCollection() throws IOException {
public void testCircle() throws IOException {
xContentTest(() -> randomCircle(randomBoolean()));
}

public void testToMap() throws IOException {
for (int i = 0; i < 10; i++) {
Geometry geometry = GeometryTestUtils.randomGeometry(randomBoolean());
XContentBuilder builder = XContentFactory.jsonBuilder();
GeoJson.toXContent(geometry, builder, ToXContent.EMPTY_PARAMS);
StreamInput input = BytesReference.bytes(builder).streamInput();

try (XContentParser parser = XContentType.JSON.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, input)) {
Map<String, Object> map = GeoJson.toMap(geometry);
assertThat(parser.map(), equalTo(map));
}
}
}
}

0 comments on commit 00a1949

Please sign in to comment.