Skip to content

Add support for geometrycollections to GeometryTreeReader and Writer #49608

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

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
Original file line number Diff line number Diff line change
Expand Up @@ -34,42 +34,50 @@
*/
public class GeometryTreeReader implements ShapeTreeReader {

private final int extentOffset = 8;
private static final int EXTENT_OFFSET = 8;
private final int startPosition;
private final ByteBufferStreamInput input;
private final CoordinateEncoder coordinateEncoder;

public GeometryTreeReader(BytesRef bytesRef, CoordinateEncoder coordinateEncoder) {
this.input = new ByteBufferStreamInput(ByteBuffer.wrap(bytesRef.bytes, bytesRef.offset, bytesRef.length));
this.startPosition = 0;
this.coordinateEncoder = coordinateEncoder;
}

private GeometryTreeReader(ByteBufferStreamInput input, CoordinateEncoder coordinateEncoder) throws IOException {
this.input = input;
startPosition = input.position();
this.coordinateEncoder = coordinateEncoder;
}

public double getCentroidX() throws IOException {
input.position(0);
input.position(startPosition);
return coordinateEncoder.decodeX(input.readInt());
}

public double getCentroidY() throws IOException {
input.position(4);
input.position(startPosition + 4);
return coordinateEncoder.decodeY(input.readInt());
}

@Override
public Extent getExtent() throws IOException {
input.position(extentOffset);
input.position(startPosition + EXTENT_OFFSET);
Extent extent = input.readOptionalWriteable(Extent::new);
if (extent != null) {
return extent;
}
assert input.readVInt() == 1;
ShapeType shapeType = input.readEnum(ShapeType.class);
ShapeTreeReader reader = getReader(shapeType, input);
ShapeTreeReader reader = getReader(shapeType, coordinateEncoder, input);
return reader.getExtent();
}

@Override
public GeoRelation relate(Extent extent) throws IOException {
GeoRelation relation = GeoRelation.QUERY_DISJOINT;
input.position(extentOffset);
input.position(startPosition + EXTENT_OFFSET);
boolean hasExtent = input.readBoolean();
if (hasExtent) {
Optional<Boolean> extentCheck = EdgeTreeReader.checkExtent(new Extent(input), extent);
Expand All @@ -79,9 +87,17 @@ public GeoRelation relate(Extent extent) throws IOException {
}

int numTrees = input.readVInt();
int nextPosition = input.position();
for (int i = 0; i < numTrees; i++) {
if (numTrees > 1) {
if (i > 0) {
input.position(nextPosition);
}
int pos = input.readVInt();
nextPosition = input.position() + pos;
}
ShapeType shapeType = input.readEnum(ShapeType.class);
ShapeTreeReader reader = getReader(shapeType, input);
ShapeTreeReader reader = getReader(shapeType, coordinateEncoder, input);
GeoRelation shapeRelation = reader.relate(extent);
if (GeoRelation.QUERY_CROSSES == shapeRelation ||
(GeoRelation.QUERY_DISJOINT == shapeRelation && GeoRelation.QUERY_INSIDE == relation)
Expand All @@ -95,7 +111,8 @@ public GeoRelation relate(Extent extent) throws IOException {
return relation;
}

private static ShapeTreeReader getReader(ShapeType shapeType, ByteBufferStreamInput input) throws IOException {
private static ShapeTreeReader getReader(ShapeType shapeType, CoordinateEncoder coordinateEncoder, ByteBufferStreamInput input)
throws IOException {
switch (shapeType) {
case POLYGON:
return new PolygonTreeReader(input);
Expand All @@ -105,6 +122,8 @@ private static ShapeTreeReader getReader(ShapeType shapeType, ByteBufferStreamIn
case LINESTRING:
case MULTILINESTRING:
return new EdgeTreeReader(input, false);
case GEOMETRYCOLLECTION:
return new GeometryTreeReader(input, coordinateEncoder);
default:
throw new UnsupportedOperationException("unsupported shape type [" + shapeType + "]");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
*/
package org.elasticsearch.common.geo;

import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.geometry.Circle;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.GeometryCollection;
Expand All @@ -32,6 +33,7 @@
import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.ShapeType;

import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -43,7 +45,7 @@
* appropriate tree structure for each type of
* {@link Geometry} into a byte array.
*/
public class GeometryTreeWriter implements Writeable {
public class GeometryTreeWriter extends ShapeTreeWriter {

private final GeometryTreeBuilder builder;
private final CoordinateEncoder coordinateEncoder;
Expand All @@ -53,29 +55,56 @@ public GeometryTreeWriter(Geometry geometry, CoordinateEncoder coordinateEncoder
this.coordinateEncoder = coordinateEncoder;
this.centroidCalculator = new CentroidCalculator();
builder = new GeometryTreeBuilder(coordinateEncoder);
geometry.visit(builder);
if (geometry.type() == ShapeType.GEOMETRYCOLLECTION) {
for (Geometry shape : (GeometryCollection<?>) geometry) {
shape.visit(builder);
}
} else {
geometry.visit(builder);
}
}

public Extent extent() {
@Override
public Extent getExtent() {
return new Extent(builder.top, builder.bottom, builder.negLeft, builder.negRight, builder.posLeft, builder.posRight);
}

@Override
public ShapeType getShapeType() {
return ShapeType.GEOMETRYCOLLECTION;
}

@Override
public CentroidCalculator getCentroidCalculator() {
return centroidCalculator;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
// only write a geometry extent for the tree if the tree
// contains multiple sub-shapes
boolean prependExtent = builder.shapeWriters.size() > 1;
boolean multiShape = builder.shapeWriters.size() > 1;
Extent extent = null;
out.writeInt(coordinateEncoder.encodeX(centroidCalculator.getX()));
out.writeInt(coordinateEncoder.encodeY(centroidCalculator.getY()));
if (prependExtent) {
if (multiShape) {
extent = new Extent(builder.top, builder.bottom, builder.negLeft, builder.negRight, builder.posLeft, builder.posRight);
}
out.writeOptionalWriteable(extent);
out.writeVInt(builder.shapeWriters.size());
for (ShapeTreeWriter writer : builder.shapeWriters) {
out.writeEnum(writer.getShapeType());
writer.writeTo(out);
if (multiShape) {
for (ShapeTreeWriter writer : builder.shapeWriters) {
try(BytesStreamOutput bytesStream = new BytesStreamOutput()) {
bytesStream.writeEnum(writer.getShapeType());
writer.writeTo(bytesStream);
BytesReference bytes = bytesStream.bytes();
out.writeVInt(bytes.length());
bytes.writeTo(out);
}
}
} else {
out.writeEnum(builder.shapeWriters.get(0).getShapeType());
builder.shapeWriters.get(0).writeTo(out);
}
}

Expand Down Expand Up @@ -110,9 +139,7 @@ private void addWriter(ShapeTreeWriter writer) {

@Override
public Void visit(GeometryCollection<?> collection) {
for (Geometry geometry : collection) {
geometry.visit(this);
}
addWriter(new GeometryTreeWriter(collection, coordinateEncoder));
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public static GeoShapeValue missing(String missing) {
try {
Geometry geometry = MISSING_GEOMETRY_PARSER.fromWKT(missing);
GeometryTreeWriter writer = new GeometryTreeWriter(geometry, GeoShapeCoordinateEncoder.INSTANCE);
return new GeoShapeValue(writer.extent());
return new GeoShapeValue(writer.getExtent());
} catch (IOException | ParseException e) {
throw new IllegalArgumentException("Can't apply missing value [" + missing + "]", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.GeometryCollection;
import org.elasticsearch.geometry.Line;
import org.elasticsearch.geometry.LinearRing;
import org.elasticsearch.geometry.MultiLine;
Expand All @@ -33,12 +34,12 @@
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.index.mapper.GeoShapeIndexer;
import org.elasticsearch.index.query.LegacyGeoShapeQueryProcessor;
import org.elasticsearch.geometry.ShapeType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.geo.RandomShapeGenerator;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -306,10 +307,6 @@ public void testRandomGeometryIntersection() throws IOException {
GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
Geometry preparedGeometry = indexer.prepareForIndexing(geometry);

// TODO: support multi-polygons
assumeFalse("polygon crosses dateline",
ShapeType.POLYGON == geometry.type() && ShapeType.MULTIPOLYGON == preparedGeometry.type());

for (int i = 0; i < testPointCount; i++) {
int cur = i;
intersects[cur] = fold(preparedGeometry, false, (g, s) -> s || intersects(g, testPoints[cur], extentSize));
Expand All @@ -329,21 +326,36 @@ private Extent bufferedExtentFromGeoPoint(double x, double y, double extentSize)
}

private boolean intersects(Geometry g, Point p, double extentSize) throws IOException {
return geometryTreeReader(g, GeoShapeCoordinateEncoder.INSTANCE)
.relate(bufferedExtentFromGeoPoint(p.getX(), p.getY(), extentSize)) == GeoRelation.QUERY_CROSSES;
GeoRelation relation = geometryTreeReader(g, GeoShapeCoordinateEncoder.INSTANCE)
.relate(bufferedExtentFromGeoPoint(p.getX(), p.getY(), extentSize));
return relation == GeoRelation.QUERY_CROSSES || relation == GeoRelation.QUERY_INSIDE;
}

private static Geometry randomGeometryTreeGeometry() {
return randomGeometryTreeGeometry(0);
}

private static Geometry randomGeometryTreeGeometry(int level) {
@SuppressWarnings("unchecked") Function<Boolean, Geometry> geometry = ESTestCase.randomFrom(
GeometryTestUtils::randomLine,
GeometryTestUtils::randomPoint,
GeometryTestUtils::randomPolygon,
GeometryTestUtils::randomMultiLine,
GeometryTestUtils::randomMultiPoint
GeometryTestUtils::randomMultiPoint,
level < 3 ? (b) -> randomGeometryTreeCollection(level + 1) : GeometryTestUtils::randomPoint // don't build too deep
);
return geometry.apply(false);
}

private static Geometry randomGeometryTreeCollection(int level) {
int size = ESTestCase.randomIntBetween(1, 10);
List<Geometry> shapes = new ArrayList<>();
for (int i = 0; i < size; i++) {
shapes.add(randomGeometryTreeGeometry(level));
}
return new GeometryCollection<>(shapes);
}

private GeometryTreeReader geometryTreeReader(Geometry geometry, CoordinateEncoder encoder) throws IOException {
GeometryTreeWriter writer = new GeometryTreeWriter(geometry, encoder);
BytesStreamOutput output = new BytesStreamOutput();
Expand Down