Skip to content

Commit

Permalink
Add visitor pattern to the triangle tree (#63333)
Browse files Browse the repository at this point in the history
In order to add more functionality to this data structure, this commit adds a visitor pattern to the tree.
  • Loading branch information
iverase committed Oct 22, 2020
1 parent c815bd8 commit 20815de
Show file tree
Hide file tree
Showing 18 changed files with 1,116 additions and 893 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,30 @@ protected static Geometry randomGeometry(int level, boolean hasAlt) {
return geometry.apply(hasAlt);
}

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

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

/**
* Extracts all vertices of the supplied geometry
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@
* Interface for classes that help encode double-valued spatial coordinates x/y to
* their integer-encoded serialized form and decode them back
*/
interface CoordinateEncoder {
public interface CoordinateEncoder {

CoordinateEncoder GEO = new GeoShapeCoordinateEncoder();

/** encode X value */
int encodeX(double x);

/** encode Y value */
int encodeY(double y);

/** decode X value */
double decodeX(int x);

/** decode Y value */
double decodeY(int y);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

import org.apache.lucene.geo.GeoEncodingUtils;

public final class GeoShapeCoordinateEncoder implements CoordinateEncoder {
public static final GeoShapeCoordinateEncoder INSTANCE = new GeoShapeCoordinateEncoder();
final class GeoShapeCoordinateEncoder implements CoordinateEncoder {

@Override
public int encodeX(double x) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.spatial.index.fielddata;

import org.apache.lucene.store.ByteArrayDataInput;
import org.apache.lucene.util.BytesRef;

/**
* A reusable Geometry doc value reader for a previous serialized {@link org.elasticsearch.geometry.Geometry} using
* {@link GeometryDocValueWriter}.
*
*
* -----------------------------------------
* | The binary format of the tree |
* -----------------------------------------
* ----------------------------------------- --
* | centroid-x-coord (4 bytes) | |
* ----------------------------------------- |
* | centroid-y-coord (4 bytes) | |
* ----------------------------------------- |
* | DimensionalShapeType (1 byte) | | Centroid-related header
* ----------------------------------------- |
* | Sum of weights (VLong 1-8 bytes) | |
* ----------------------------------------- --
* | Extent (var-encoding) |
* -----------------------------------------
* | Triangle Tree |
* -----------------------------------------
* -----------------------------------------
*/
public class GeometryDocValueReader {
private final ByteArrayDataInput input;
private final Extent extent;
private int treeOffset;
private int docValueOffset;

public GeometryDocValueReader() {
this.extent = new Extent();
this.input = new ByteArrayDataInput();
}

/**
* reset the geometry.
*/
public void reset(BytesRef bytesRef) {
this.input.reset(bytesRef.bytes, bytesRef.offset, bytesRef.length);
docValueOffset = bytesRef.offset;
treeOffset = 0;
}

/**
* returns the {@link Extent} of this geometry.
*/
protected Extent getExtent() {
if (treeOffset == 0) {
getSumCentroidWeight(); // skip CENTROID_HEADER + var-long sum-weight
Extent.readFromCompressed(input, extent);
treeOffset = input.getPosition();
} else {
input.setPosition(treeOffset);
}
return extent;
}

/**
* returns the encoded X coordinate of the centroid.
*/
protected int getCentroidX() {
input.setPosition(docValueOffset + 0);
return input.readInt();
}

/**
* returns the encoded Y coordinate of the centroid.
*/
protected int getCentroidY() {
input.setPosition(docValueOffset + 4);
return input.readInt();
}

protected DimensionalShapeType getDimensionalShapeType() {
input.setPosition(docValueOffset + 8);
return DimensionalShapeType.readFrom(input);
}

protected double getSumCentroidWeight() {
input.setPosition(docValueOffset + 9);
return Double.longBitsToDouble(input.readVLong());
}

/**
* Visit the triangle tree with the provided visitor
*/
protected void visit(TriangleTreeReader.Visitor visitor) {
Extent extent = getExtent();
int thisMaxX = extent.maxX();
int thisMinX = extent.minX();
int thisMaxY = extent.maxY();
int thisMinY = extent.minY();
if(visitor.push(thisMinX, thisMinY, thisMaxX, thisMaxY)) {
TriangleTreeReader.visit(input, visitor, thisMaxX, thisMaxY);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.spatial.index.fielddata;

import org.apache.lucene.document.ShapeField;
import org.apache.lucene.store.ByteBuffersDataOutput;

import java.io.IOException;
import java.util.List;

/**
* This is a tree-writer that serializes a list of {@link ShapeField.DecodedTriangle} as an interval tree
* into a byte array.
*/
public class GeometryDocValueWriter {

private final TriangleTreeWriter treeWriter;
private final CoordinateEncoder coordinateEncoder;
private final CentroidCalculator centroidCalculator;

public GeometryDocValueWriter(List<ShapeField.DecodedTriangle> triangles, CoordinateEncoder coordinateEncoder,
CentroidCalculator centroidCalculator) {
this.coordinateEncoder = coordinateEncoder;
this.centroidCalculator = centroidCalculator;
this.treeWriter = new TriangleTreeWriter(triangles);
}

/*** Serialize the interval tree in the provided data output */
public void writeTo(ByteBuffersDataOutput out) throws IOException {
out.writeInt(coordinateEncoder.encodeX(centroidCalculator.getX()));
out.writeInt(coordinateEncoder.encodeY(centroidCalculator.getY()));
centroidCalculator.getDimensionalShapeType().writeTo(out);
out.writeVLong(Double.doubleToLongBits(centroidCalculator.sumWeight()));
treeWriter.writeTo(out);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void close() {
public MultiGeoShapeValues getGeoShapeValues() {
try {
final BinaryDocValues binaryValues = DocValues.getBinary(reader, fieldName);
final TriangleTreeReader reader = new TriangleTreeReader(GeoShapeCoordinateEncoder.INSTANCE);
final GeometryDocValueReader reader = new GeometryDocValueReader();
final MultiGeoShapeValues.GeoShapeValue geoShapeValue = new MultiGeoShapeValues.GeoShapeValue(reader);
return new MultiGeoShapeValues() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.store.ByteBuffersDataOutput;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.GeographyValidator;
Expand Down Expand Up @@ -93,31 +92,34 @@ protected MultiGeoShapeValues() {
*/
public abstract GeoShapeValue nextValue() throws IOException;

/** thin wrapper around a {@link GeometryDocValueReader} which encodes / decodes values using
* the Geo decoder */
public static class GeoShapeValue {
private static final WellKnownText MISSING_GEOMETRY_PARSER = new WellKnownText(true, new GeographyValidator(true));

private final TriangleTreeReader reader;
private final GeometryDocValueReader reader;
private final BoundingBox boundingBox;
private final Tile2DVisitor tile2DVisitor;

public GeoShapeValue(TriangleTreeReader reader) {
public GeoShapeValue(GeometryDocValueReader reader) {
this.reader = reader;
this.boundingBox = new BoundingBox();
tile2DVisitor = new Tile2DVisitor();
}

public BoundingBox boundingBox() {
boundingBox.reset(reader.getExtent(), GeoShapeCoordinateEncoder.INSTANCE);
boundingBox.reset(reader.getExtent(), CoordinateEncoder.GEO);
return boundingBox;
}

/**
* @return the latitude of the centroid of the shape
*/
public GeoRelation relate(Rectangle rectangle) {
int minX = GeoShapeCoordinateEncoder.INSTANCE.encodeX(rectangle.getMinX());
int maxX = GeoShapeCoordinateEncoder.INSTANCE.encodeX(rectangle.getMaxX());
int minY = GeoShapeCoordinateEncoder.INSTANCE.encodeY(rectangle.getMinY());
int maxY = GeoShapeCoordinateEncoder.INSTANCE.encodeY(rectangle.getMaxY());
return reader.relateTile(minX, minY, maxX, maxY);
int minX = CoordinateEncoder.GEO.encodeX(rectangle.getMinX());
int maxX = CoordinateEncoder.GEO.encodeX(rectangle.getMaxX());
int minY = CoordinateEncoder.GEO.encodeY(rectangle.getMinY());
int maxY = CoordinateEncoder.GEO.encodeY(rectangle.getMaxY());
tile2DVisitor.reset(minX, minY, maxX, maxY);
reader.visit(tile2DVisitor);
return tile2DVisitor.relation();
}

public DimensionalShapeType dimensionalShapeType() {
Expand All @@ -128,27 +130,30 @@ public double weight() {
return reader.getSumCentroidWeight();
}

/**
* @return the latitude of the centroid of the shape
*/
public double lat() {
return reader.getCentroidY();
return CoordinateEncoder.GEO.decodeY(reader.getCentroidY());
}

/**
* @return the longitude of the centroid of the shape
*/
public double lon() {
return reader.getCentroidX();
return CoordinateEncoder.GEO.decodeX(reader.getCentroidX());
}

public static GeoShapeValue missing(String missing) {
try {
Geometry geometry = MISSING_GEOMETRY_PARSER.fromWKT(missing);
ShapeField.DecodedTriangle[] triangles = toDecodedTriangles(geometry);
TriangleTreeWriter writer =
new TriangleTreeWriter(Arrays.asList(triangles), GeoShapeCoordinateEncoder.INSTANCE,
GeometryDocValueWriter writer =
new GeometryDocValueWriter(Arrays.asList(triangles), CoordinateEncoder.GEO,
new CentroidCalculator(geometry));
ByteBuffersDataOutput output = new ByteBuffersDataOutput();
writer.writeTo(output);
TriangleTreeReader reader = new TriangleTreeReader(GeoShapeCoordinateEncoder.INSTANCE);
GeometryDocValueReader reader = new GeometryDocValueReader();
reader.reset(new BytesRef(output.toArrayCopy(), 0, Math.toIntExact(output.size())));
return new GeoShapeValue(reader);
} catch (IOException | ParseException e) {
Expand Down Expand Up @@ -204,22 +209,6 @@ private void reset(Extent extent, CoordinateEncoder coordinateEncoder) {
}
}

private void reset(GeoPoint point) {
this.top = point.lat();
this.bottom = point.lat();
if (point.lon() < 0) {
this.negLeft = point.lon();
this.negRight = point.lon();
this.posLeft = Double.POSITIVE_INFINITY;
this.posRight = Double.NEGATIVE_INFINITY;
} else {
this.negLeft = Double.POSITIVE_INFINITY;
this.negRight = Double.NEGATIVE_INFINITY;
this.posLeft = point.lon();
this.posRight = point.lon();
}
}

/**
* @return the minimum y-coordinate of the extent
*/
Expand Down

0 comments on commit 20815de

Please sign in to comment.