Skip to content

Commit 51cc89d

Browse files
authored
Add brute force approach to GeoHashGridTiler (#96863)
Resolve the tiler using brute force approach when a geometry only touches a few hashes
1 parent 4e4b95d commit 51cc89d

File tree

2 files changed

+44
-35
lines changed

2 files changed

+44
-35
lines changed

docs/changelog/96863.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 96863
2+
summary: Add brute force approach to `GeoHashGridTiler`
3+
area: Geo
4+
type: enhancement
5+
issues: []

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoHashGridTiler.java

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ public long encode(double x, double y) {
4343

4444
@Override
4545
public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geoValue) throws IOException {
46-
4746
if (precision == 0) {
4847
return 1;
4948
}
@@ -52,60 +51,65 @@ public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geo
5251

5352
// When the shape represents a point, we compute the hash directly as we do it for GeoPoint
5453
if (bounds.minX() == bounds.maxX() && bounds.minY() == bounds.maxY()) {
55-
return setValue(values, geoValue, bounds);
54+
return setValue(values, geoValue, Geohash.stringEncode(bounds.minX(), bounds.minY(), precision), 0);
55+
}
56+
final long dX = (long) Math.ceil((bounds.maxX() - bounds.minX()) / Geohash.lonWidthInDegrees(precision));
57+
final long dY = (long) Math.ceil((bounds.maxY() - bounds.minY()) / Geohash.latHeightInDegrees(precision));
58+
if (dX * dY <= 32L * precision) {
59+
return setValuesByBruteForceScan(values, geoValue, bounds);
60+
} else {
61+
return setValuesByRasterization("", values, 0, geoValue);
5662
}
57-
// TODO: optimize for when a shape fits in a single tile an
58-
// for when brute-force is expected to be faster than rasterization, which
59-
// is when the number of tiles expected is less than the precision
60-
return setValuesByRasterization("", values, 0, geoValue);
6163
}
6264

65+
/**
66+
* Checks all hashes between minX/maxX and minY/maxY
67+
*/
68+
// pkg protected for testing
6369
int setValuesByBruteForceScan(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geoValue, GeoShapeValues.BoundingBox bounds)
6470
throws IOException {
65-
// TODO: This way to discover cells inside of a bounding box seems not to work as expected. I can
66-
// see that eventually we will be visiting twice the same cell which should not happen.
71+
final String stop = Geohash.stringEncode(bounds.maxX(), bounds.maxY(), precision);
72+
String firstInRow = null;
73+
String lastInRow = null;
6774
int idx = 0;
68-
String min = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
69-
String max = Geohash.stringEncode(bounds.maxX(), bounds.maxY(), precision);
70-
String minNeighborBelow = Geohash.getNeighbor(min, precision, 0, -1);
71-
double minY = Geohash.decodeLatitude((minNeighborBelow == null) ? min : minNeighborBelow);
72-
double minX = Geohash.decodeLongitude(min);
73-
double maxY = Geohash.decodeLatitude(max);
74-
double maxX = Geohash.decodeLongitude(max);
75-
for (double i = minX; i <= maxX; i += Geohash.lonWidthInDegrees(precision)) {
76-
for (double j = minY; j <= maxY; j += Geohash.latHeightInDegrees(precision)) {
77-
String hash = Geohash.stringEncode(i, j, precision);
78-
GeoRelation relation = relateTile(geoValue, hash);
79-
if (relation != GeoRelation.QUERY_DISJOINT) {
80-
values.resizeCell(idx + 1);
81-
values.add(idx++, encode(i, j));
75+
do {
76+
lastInRow = moveDown(lastInRow, precision, bounds.maxX(), bounds.minY());
77+
String current = null;
78+
do {
79+
if (current == null) {
80+
firstInRow = moveDown(firstInRow, precision, bounds.minX(), bounds.minY());
81+
current = firstInRow;
82+
} else {
83+
current = Geohash.getNeighbor(current, precision, 1, 0);
8284
}
83-
}
84-
}
85+
idx = setValue(values, geoValue, current, idx);
86+
} while (current.equals(lastInRow) == false);
87+
} while (lastInRow.equals(stop) == false);
8588
return idx;
8689
}
8790

91+
private static String moveDown(String hash, int precision, double x, double y) {
92+
return hash == null ? Geohash.stringEncode(x, y, precision) : Geohash.getNeighbor(hash, precision, 0, 1);
93+
}
94+
8895
/**
8996
* Sets a singular doc-value for the {@link GeoShapeValues.GeoShapeValue}.
9097
*/
91-
private int setValue(GeoShapeCellValues docValues, GeoShapeValues.GeoShapeValue geoValue, GeoShapeValues.BoundingBox bounds)
92-
throws IOException {
93-
String hash = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
98+
private int setValue(GeoShapeCellValues docValues, GeoShapeValues.GeoShapeValue geoValue, String hash, int idx) throws IOException {
9499
if (relateTile(geoValue, hash) != GeoRelation.QUERY_DISJOINT) {
95-
docValues.resizeCell(1);
96-
docValues.add(0, Geohash.longEncode(hash));
97-
return 1;
100+
docValues.resizeCell(idx + 1);
101+
docValues.add(idx++, Geohash.longEncode(hash));
98102
}
99-
return 0;
103+
return idx;
100104
}
101105

102106
private GeoRelation relateTile(GeoShapeValues.GeoShapeValue geoValue, String hash) throws IOException {
103107
if (validHash(hash)) {
104108
final Rectangle rectangle = Geohash.toBoundingBox(hash);
105-
int minX = GeoEncodingUtils.encodeLongitude(rectangle.getMinLon());
106-
int minY = GeoEncodingUtils.encodeLatitude(rectangle.getMinLat());
107-
int maxX = GeoEncodingUtils.encodeLongitude(rectangle.getMaxLon());
108-
int maxY = GeoEncodingUtils.encodeLatitude(rectangle.getMaxLat());
109+
final int minX = GeoEncodingUtils.encodeLongitude(rectangle.getMinLon());
110+
final int minY = GeoEncodingUtils.encodeLatitude(rectangle.getMinLat());
111+
final int maxX = GeoEncodingUtils.encodeLongitude(rectangle.getMaxLon());
112+
final int maxY = GeoEncodingUtils.encodeLatitude(rectangle.getMaxLat());
109113
return geoValue.relate(minX, maxX == Integer.MAX_VALUE ? maxX : maxX - 1, minY, maxY == Integer.MAX_VALUE ? maxY : maxY - 1);
110114
}
111115
return GeoRelation.QUERY_DISJOINT;

0 commit comments

Comments
 (0)