Skip to content

Commit cad8817

Browse files
authored
Add visitor pattern to the triangle tree (elastic#63333) (elastic#64049)
In order to add more functionality to this data structure, this commit adds a visitor pattern to the tree.
1 parent 7eb3a77 commit cad8817

18 files changed

+1117
-893
lines changed

test/framework/src/main/java/org/elasticsearch/geo/GeometryTestUtils.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,30 @@ protected static Geometry randomGeometry(int level, boolean hasAlt) {
203203
return geometry.apply(hasAlt);
204204
}
205205

206+
public static Geometry randomGeometryWithoutCircle(int level, boolean hasAlt) {
207+
@SuppressWarnings("unchecked") Function<Boolean, Geometry> geometry = ESTestCase.randomFrom(
208+
GeometryTestUtils::randomPoint,
209+
GeometryTestUtils::randomMultiPoint,
210+
GeometryTestUtils::randomLine,
211+
GeometryTestUtils::randomMultiLine,
212+
GeometryTestUtils::randomPolygon,
213+
GeometryTestUtils::randomMultiPolygon,
214+
hasAlt ? GeometryTestUtils::randomPoint : (b) -> randomRectangle(),
215+
level < 3 ? (b) ->
216+
randomGeometryWithoutCircleCollection(level + 1, hasAlt) : GeometryTestUtils::randomPoint // don't build too deep
217+
);
218+
return geometry.apply(hasAlt);
219+
}
220+
221+
private static Geometry randomGeometryWithoutCircleCollection(int level, boolean hasAlt) {
222+
int size = ESTestCase.randomIntBetween(1, 10);
223+
List<Geometry> shapes = new ArrayList<>();
224+
for (int i = 0; i < size; i++) {
225+
shapes.add(randomGeometryWithoutCircle(level, hasAlt));
226+
}
227+
return new GeometryCollection<>(shapes);
228+
}
229+
206230
/**
207231
* Extracts all vertices of the supplied geometry
208232
*/

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/CoordinateEncoder.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,19 @@
1010
* Interface for classes that help encode double-valued spatial coordinates x/y to
1111
* their integer-encoded serialized form and decode them back
1212
*/
13-
interface CoordinateEncoder {
13+
public interface CoordinateEncoder {
14+
15+
CoordinateEncoder GEO = new GeoShapeCoordinateEncoder();
16+
17+
/** encode X value */
1418
int encodeX(double x);
19+
20+
/** encode Y value */
1521
int encodeY(double y);
22+
23+
/** decode X value */
1624
double decodeX(int x);
25+
26+
/** decode Y value */
1727
double decodeY(int y);
1828
}

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/GeoShapeCoordinateEncoder.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
import org.apache.lucene.geo.GeoEncodingUtils;
1010

11-
public final class GeoShapeCoordinateEncoder implements CoordinateEncoder {
12-
public static final GeoShapeCoordinateEncoder INSTANCE = new GeoShapeCoordinateEncoder();
11+
final class GeoShapeCoordinateEncoder implements CoordinateEncoder {
1312

1413
@Override
1514
public int encodeX(double x) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.spatial.index.fielddata;
8+
9+
import org.apache.lucene.store.ByteArrayDataInput;
10+
import org.apache.lucene.util.BytesRef;
11+
12+
/**
13+
* A reusable Geometry doc value reader for a previous serialized {@link org.elasticsearch.geometry.Geometry} using
14+
* {@link GeometryDocValueWriter}.
15+
*
16+
*
17+
* -----------------------------------------
18+
* | The binary format of the tree |
19+
* -----------------------------------------
20+
* ----------------------------------------- --
21+
* | centroid-x-coord (4 bytes) | |
22+
* ----------------------------------------- |
23+
* | centroid-y-coord (4 bytes) | |
24+
* ----------------------------------------- |
25+
* | DimensionalShapeType (1 byte) | | Centroid-related header
26+
* ----------------------------------------- |
27+
* | Sum of weights (VLong 1-8 bytes) | |
28+
* ----------------------------------------- --
29+
* | Extent (var-encoding) |
30+
* -----------------------------------------
31+
* | Triangle Tree |
32+
* -----------------------------------------
33+
* -----------------------------------------
34+
*/
35+
public class GeometryDocValueReader {
36+
private final ByteArrayDataInput input;
37+
private final Extent extent;
38+
private int treeOffset;
39+
private int docValueOffset;
40+
41+
public GeometryDocValueReader() {
42+
this.extent = new Extent();
43+
this.input = new ByteArrayDataInput();
44+
}
45+
46+
/**
47+
* reset the geometry.
48+
*/
49+
public void reset(BytesRef bytesRef) {
50+
this.input.reset(bytesRef.bytes, bytesRef.offset, bytesRef.length);
51+
docValueOffset = bytesRef.offset;
52+
treeOffset = 0;
53+
}
54+
55+
/**
56+
* returns the {@link Extent} of this geometry.
57+
*/
58+
protected Extent getExtent() {
59+
if (treeOffset == 0) {
60+
getSumCentroidWeight(); // skip CENTROID_HEADER + var-long sum-weight
61+
Extent.readFromCompressed(input, extent);
62+
treeOffset = input.getPosition();
63+
} else {
64+
input.setPosition(treeOffset);
65+
}
66+
return extent;
67+
}
68+
69+
/**
70+
* returns the encoded X coordinate of the centroid.
71+
*/
72+
protected int getCentroidX() {
73+
input.setPosition(docValueOffset + 0);
74+
return input.readInt();
75+
}
76+
77+
/**
78+
* returns the encoded Y coordinate of the centroid.
79+
*/
80+
protected int getCentroidY() {
81+
input.setPosition(docValueOffset + 4);
82+
return input.readInt();
83+
}
84+
85+
protected DimensionalShapeType getDimensionalShapeType() {
86+
input.setPosition(docValueOffset + 8);
87+
return DimensionalShapeType.readFrom(input);
88+
}
89+
90+
protected double getSumCentroidWeight() {
91+
input.setPosition(docValueOffset + 9);
92+
return Double.longBitsToDouble(input.readVLong());
93+
}
94+
95+
/**
96+
* Visit the triangle tree with the provided visitor
97+
*/
98+
protected void visit(TriangleTreeReader.Visitor visitor) {
99+
Extent extent = getExtent();
100+
int thisMaxX = extent.maxX();
101+
int thisMinX = extent.minX();
102+
int thisMaxY = extent.maxY();
103+
int thisMinY = extent.minY();
104+
if(visitor.push(thisMinX, thisMinY, thisMaxX, thisMaxY)) {
105+
TriangleTreeReader.visit(input, visitor, thisMaxX, thisMaxY);
106+
}
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.spatial.index.fielddata;
8+
9+
import org.apache.lucene.document.ShapeField;
10+
import org.apache.lucene.store.ByteBuffersDataOutput;
11+
12+
import java.io.IOException;
13+
import java.util.List;
14+
15+
/**
16+
* This is a tree-writer that serializes a list of {@link ShapeField.DecodedTriangle} as an interval tree
17+
* into a byte array.
18+
*/
19+
public class GeometryDocValueWriter {
20+
21+
private final TriangleTreeWriter treeWriter;
22+
private final CoordinateEncoder coordinateEncoder;
23+
private final CentroidCalculator centroidCalculator;
24+
25+
public GeometryDocValueWriter(List<ShapeField.DecodedTriangle> triangles, CoordinateEncoder coordinateEncoder,
26+
CentroidCalculator centroidCalculator) {
27+
this.coordinateEncoder = coordinateEncoder;
28+
this.centroidCalculator = centroidCalculator;
29+
this.treeWriter = new TriangleTreeWriter(triangles);
30+
}
31+
32+
/*** Serialize the interval tree in the provided data output */
33+
public void writeTo(ByteBuffersDataOutput out) throws IOException {
34+
out.writeInt(coordinateEncoder.encodeX(centroidCalculator.getX()));
35+
out.writeInt(coordinateEncoder.encodeY(centroidCalculator.getY()));
36+
centroidCalculator.getDimensionalShapeType().writeTo(out);
37+
out.writeVLong(Double.doubleToLongBits(centroidCalculator.sumWeight()));
38+
treeWriter.writeTo(out);
39+
}
40+
}

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LatLonShapeDVAtomicShapeFieldData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public void close() {
4747
public MultiGeoShapeValues getGeoShapeValues() {
4848
try {
4949
final BinaryDocValues binaryValues = DocValues.getBinary(reader, fieldName);
50-
final TriangleTreeReader reader = new TriangleTreeReader(GeoShapeCoordinateEncoder.INSTANCE);
50+
final GeometryDocValueReader reader = new GeometryDocValueReader();
5151
final MultiGeoShapeValues.GeoShapeValue geoShapeValue = new MultiGeoShapeValues.GeoShapeValue(reader);
5252
return new MultiGeoShapeValues() {
5353

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/MultiGeoShapeValues.java

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import org.apache.lucene.index.IndexableField;
1111
import org.apache.lucene.store.ByteBuffersDataOutput;
1212
import org.apache.lucene.util.BytesRef;
13-
import org.elasticsearch.common.geo.GeoPoint;
1413
import org.elasticsearch.geometry.Geometry;
1514
import org.elasticsearch.geometry.Rectangle;
1615
import org.elasticsearch.geometry.utils.GeographyValidator;
@@ -93,31 +92,34 @@ protected MultiGeoShapeValues() {
9392
*/
9493
public abstract GeoShapeValue nextValue() throws IOException;
9594

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

99-
private final TriangleTreeReader reader;
100+
private final GeometryDocValueReader reader;
100101
private final BoundingBox boundingBox;
102+
private final Tile2DVisitor tile2DVisitor;
101103

102-
public GeoShapeValue(TriangleTreeReader reader) {
104+
public GeoShapeValue(GeometryDocValueReader reader) {
103105
this.reader = reader;
104106
this.boundingBox = new BoundingBox();
107+
tile2DVisitor = new Tile2DVisitor();
105108
}
106109

107110
public BoundingBox boundingBox() {
108-
boundingBox.reset(reader.getExtent(), GeoShapeCoordinateEncoder.INSTANCE);
111+
boundingBox.reset(reader.getExtent(), CoordinateEncoder.GEO);
109112
return boundingBox;
110113
}
111114

112-
/**
113-
* @return the latitude of the centroid of the shape
114-
*/
115115
public GeoRelation relate(Rectangle rectangle) {
116-
int minX = GeoShapeCoordinateEncoder.INSTANCE.encodeX(rectangle.getMinX());
117-
int maxX = GeoShapeCoordinateEncoder.INSTANCE.encodeX(rectangle.getMaxX());
118-
int minY = GeoShapeCoordinateEncoder.INSTANCE.encodeY(rectangle.getMinY());
119-
int maxY = GeoShapeCoordinateEncoder.INSTANCE.encodeY(rectangle.getMaxY());
120-
return reader.relateTile(minX, minY, maxX, maxY);
116+
int minX = CoordinateEncoder.GEO.encodeX(rectangle.getMinX());
117+
int maxX = CoordinateEncoder.GEO.encodeX(rectangle.getMaxX());
118+
int minY = CoordinateEncoder.GEO.encodeY(rectangle.getMinY());
119+
int maxY = CoordinateEncoder.GEO.encodeY(rectangle.getMaxY());
120+
tile2DVisitor.reset(minX, minY, maxX, maxY);
121+
reader.visit(tile2DVisitor);
122+
return tile2DVisitor.relation();
121123
}
122124

123125
public DimensionalShapeType dimensionalShapeType() {
@@ -128,27 +130,30 @@ public double weight() {
128130
return reader.getSumCentroidWeight();
129131
}
130132

133+
/**
134+
* @return the latitude of the centroid of the shape
135+
*/
131136
public double lat() {
132-
return reader.getCentroidY();
137+
return CoordinateEncoder.GEO.decodeY(reader.getCentroidY());
133138
}
134139

135140
/**
136141
* @return the longitude of the centroid of the shape
137142
*/
138143
public double lon() {
139-
return reader.getCentroidX();
144+
return CoordinateEncoder.GEO.decodeX(reader.getCentroidX());
140145
}
141146

142147
public static GeoShapeValue missing(String missing) {
143148
try {
144149
Geometry geometry = MISSING_GEOMETRY_PARSER.fromWKT(missing);
145150
ShapeField.DecodedTriangle[] triangles = toDecodedTriangles(geometry);
146-
TriangleTreeWriter writer =
147-
new TriangleTreeWriter(Arrays.asList(triangles), GeoShapeCoordinateEncoder.INSTANCE,
151+
GeometryDocValueWriter writer =
152+
new GeometryDocValueWriter(Arrays.asList(triangles), CoordinateEncoder.GEO,
148153
new CentroidCalculator(geometry));
149154
ByteBuffersDataOutput output = new ByteBuffersDataOutput();
150155
writer.writeTo(output);
151-
TriangleTreeReader reader = new TriangleTreeReader(GeoShapeCoordinateEncoder.INSTANCE);
156+
GeometryDocValueReader reader = new GeometryDocValueReader();
152157
reader.reset(new BytesRef(output.toArrayCopy(), 0, Math.toIntExact(output.size())));
153158
return new GeoShapeValue(reader);
154159
} catch (IOException | ParseException e) {
@@ -204,22 +209,6 @@ private void reset(Extent extent, CoordinateEncoder coordinateEncoder) {
204209
}
205210
}
206211

207-
private void reset(GeoPoint point) {
208-
this.top = point.lat();
209-
this.bottom = point.lat();
210-
if (point.lon() < 0) {
211-
this.negLeft = point.lon();
212-
this.negRight = point.lon();
213-
this.posLeft = Double.POSITIVE_INFINITY;
214-
this.posRight = Double.NEGATIVE_INFINITY;
215-
} else {
216-
this.negLeft = Double.POSITIVE_INFINITY;
217-
this.negRight = Double.NEGATIVE_INFINITY;
218-
this.posLeft = point.lon();
219-
this.posRight = point.lon();
220-
}
221-
}
222-
223212
/**
224213
* @return the minimum y-coordinate of the extent
225214
*/

0 commit comments

Comments
 (0)