Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streamline GeoJSON to map serialization #60413

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}
}
}