8
8
9
9
package org .opensearch .geo .search .aggregations .bucket ;
10
10
11
+ import com .carrotsearch .hppc .ObjectIntHashMap ;
12
+ import com .carrotsearch .hppc .ObjectIntMap ;
11
13
import org .opensearch .Version ;
12
14
import org .opensearch .action .index .IndexRequestBuilder ;
15
+ import org .opensearch .cluster .metadata .IndexMetadata ;
16
+ import org .opensearch .common .geo .GeoPoint ;
17
+ import org .opensearch .common .geo .GeoShapeDocValue ;
18
+ import org .opensearch .common .settings .Settings ;
13
19
import org .opensearch .common .xcontent .XContentBuilder ;
14
20
import org .opensearch .geo .GeoModulePluginIntegTestCase ;
21
+ import org .opensearch .geo .tests .common .RandomGeoGenerator ;
22
+ import org .opensearch .geo .tests .common .RandomGeoGeometryGenerator ;
15
23
import org .opensearch .geometry .Geometry ;
16
24
import org .opensearch .geometry .Rectangle ;
17
25
import org .opensearch .test .VersionUtils ;
18
26
27
+ import java .util .ArrayList ;
28
+ import java .util .HashSet ;
19
29
import java .util .List ;
30
+ import java .util .Random ;
31
+ import java .util .Set ;
20
32
21
33
import static org .opensearch .common .xcontent .XContentFactory .jsonBuilder ;
34
+ import static org .opensearch .test .hamcrest .OpenSearchAssertions .assertAcked ;
22
35
23
36
/**
24
37
* This is the base class for all the Bucket Aggregation related integration tests. Use this class to add common
@@ -33,33 +46,169 @@ public abstract class AbstractBucketAggregationIntegTest extends GeoModulePlugin
33
46
34
47
protected static final String GEO_SHAPE_INDEX_NAME = "geoshape_index" ;
35
48
36
- // We don't want to generate this BB at random as it may lead to generation of very large or very small BB.
37
- // Hence, we are making the parameters of this BB static for simplicity.
38
- protected static final Rectangle BOUNDING_RECTANGLE_FOR_GEO_SHAPES_AGG = new Rectangle (-4.1 , 20.9 , 21.9 , -3.1 );
49
+ protected static Rectangle boundingRectangleForGeoShapesAgg ;
39
50
40
- protected static final String AGG_NAME = "geohashgrid" ;
51
+ protected static ObjectIntMap <String > expectedDocsCountForGeoShapes ;
52
+
53
+ protected static ObjectIntMap <String > expectedDocCountsForSingleGeoPoint ;
54
+
55
+ protected static ObjectIntMap <String > multiValuedExpectedDocCountsGeoPoint ;
41
56
42
57
protected static final String GEO_SHAPE_FIELD_NAME = "location_geo_shape" ;
43
58
44
59
protected static final String GEO_POINT_FIELD_NAME = "location" ;
45
60
46
61
protected static final String KEYWORD_FIELD_NAME = "city" ;
47
62
63
+ protected static String smallestGeoHash = null ;
64
+
48
65
protected final Version version = VersionUtils .randomIndexCompatibleVersion (random ());
49
66
50
67
@ Override
51
68
protected boolean forbidPrivateIndexSettings () {
52
69
return false ;
53
70
}
54
71
72
+ /**
73
+ * Prepares a GeoShape index for testing the GeoShape bucket aggregations. Different bucket aggregations can use
74
+ * different techniques for creating buckets. Override the method
75
+ * {@link AbstractBucketAggregationIntegTest#generateBucketsForGeometry} in the test class for creating the
76
+ * buckets which will then be used for verifications.
77
+ *
78
+ * @param random {@link Random}
79
+ * @throws Exception thrown during index creation.
80
+ */
81
+ protected void prepareGeoShapeIndexForAggregations (final Random random ) throws Exception {
82
+ expectedDocsCountForGeoShapes = new ObjectIntHashMap <>();
83
+ final Settings settings = Settings .builder ().put (IndexMetadata .SETTING_VERSION_CREATED , version ).build ();
84
+ final List <IndexRequestBuilder > geoshapes = new ArrayList <>();
85
+ assertAcked (prepareCreate (GEO_SHAPE_INDEX_NAME ).setSettings (settings ).setMapping (GEO_SHAPE_FIELD_NAME , "type" + "=geo_shape" ));
86
+ boolean isShapeIntersectingBB = false ;
87
+ for (int i = 0 ; i < NUM_DOCS ;) {
88
+ final Geometry geometry = RandomGeoGeometryGenerator .randomGeometry (random );
89
+ final GeoShapeDocValue geometryDocValue = GeoShapeDocValue .createGeometryDocValue (geometry );
90
+ // make sure that there is 1 shape is intersecting with the bounding box
91
+ if (!isShapeIntersectingBB ) {
92
+ isShapeIntersectingBB = geometryDocValue .isIntersectingRectangle (boundingRectangleForGeoShapesAgg );
93
+ if (!isShapeIntersectingBB && i == NUM_DOCS - 1 ) {
94
+ continue ;
95
+ }
96
+ }
97
+ i ++;
98
+ final Set <String > values = generateBucketsForGeometry (geometry , geometryDocValue );
99
+ geoshapes .add (indexGeoShape (GEO_SHAPE_INDEX_NAME , geometry ));
100
+ for (final String hash : values ) {
101
+ expectedDocsCountForGeoShapes .put (hash , expectedDocsCountForGeoShapes .getOrDefault (hash , 0 ) + 1 );
102
+ }
103
+ }
104
+ indexRandom (true , geoshapes );
105
+ ensureGreen (GEO_SHAPE_INDEX_NAME );
106
+ }
107
+
108
+ /**
109
+ * Returns a set of buckets for the shape at different precision level. Override this method for different bucket
110
+ * aggregations.
111
+ *
112
+ * @param geometry {@link Geometry}
113
+ * @param geoShapeDocValue {@link GeoShapeDocValue}
114
+ * @return A {@link Set} of {@link String} which represents the buckets.
115
+ */
116
+ protected abstract Set <String > generateBucketsForGeometry (final Geometry geometry , final GeoShapeDocValue geoShapeDocValue );
117
+
118
+ /**
119
+ * Prepares a GeoPoint index for testing the GeoPoint bucket aggregations. Different bucket aggregations can use
120
+ * different techniques for creating buckets. Override the method
121
+ * {@link AbstractBucketAggregationIntegTest#generateBucketsForGeoPoint} in the test class for creating the
122
+ * buckets which will then be used for verifications.
123
+ *
124
+ * @param random {@link Random}
125
+ * @throws Exception thrown during index creation.
126
+ */
127
+ protected void prepareSingleValueGeoPointIndex (final Random random ) throws Exception {
128
+ expectedDocCountsForSingleGeoPoint = new ObjectIntHashMap <>();
129
+ createIndex ("idx_unmapped" );
130
+ final Settings settings = Settings .builder ()
131
+ .put (IndexMetadata .SETTING_VERSION_CREATED , version )
132
+ .put ("index.number_of_shards" , 4 )
133
+ .put ("index.number_of_replicas" , 0 )
134
+ .build ();
135
+ assertAcked (
136
+ prepareCreate ("idx" ).setSettings (settings )
137
+ .setMapping (GEO_POINT_FIELD_NAME , "type=geo_point" , KEYWORD_FIELD_NAME , "type=keyword" )
138
+ );
139
+ final List <IndexRequestBuilder > cities = new ArrayList <>();
140
+ for (int i = 0 ; i < NUM_DOCS ; i ++) {
141
+ // generate random point
142
+ final GeoPoint geoPoint = RandomGeoGenerator .randomPoint (random );
143
+ cities .add (indexGeoPoint ("idx" , geoPoint .toString (), geoPoint .getLat () + ", " + geoPoint .getLon ()));
144
+ final Set <String > buckets = generateBucketsForGeoPoint (geoPoint );
145
+ for (final String bucket : buckets ) {
146
+ expectedDocCountsForSingleGeoPoint .put (bucket , expectedDocCountsForSingleGeoPoint .getOrDefault (bucket , 0 ) + 1 );
147
+ }
148
+ }
149
+ indexRandom (true , cities );
150
+ ensureGreen ("idx_unmapped" , "idx" );
151
+ }
152
+
153
+ protected void prepareMultiValuedGeoPointIndex (final Random random ) throws Exception {
154
+ multiValuedExpectedDocCountsGeoPoint = new ObjectIntHashMap <>();
155
+ final Settings settings = Settings .builder ().put (IndexMetadata .SETTING_VERSION_CREATED , version ).build ();
156
+ final List <IndexRequestBuilder > cities = new ArrayList <>();
157
+ assertAcked (
158
+ prepareCreate ("multi_valued_idx" ).setSettings (settings )
159
+ .setMapping (GEO_POINT_FIELD_NAME , "type=geo_point" , KEYWORD_FIELD_NAME , "type=keyword" )
160
+ );
161
+ for (int i = 0 ; i < NUM_DOCS ; i ++) {
162
+ final int numPoints = random .nextInt (4 );
163
+ final List <String > points = new ArrayList <>();
164
+ final Set <String > buckets = new HashSet <>();
165
+ for (int j = 0 ; j < numPoints ; ++j ) {
166
+ // generate random point
167
+ final GeoPoint geoPoint = RandomGeoGenerator .randomPoint (random );
168
+ points .add (geoPoint .getLat () + "," + geoPoint .getLon ());
169
+ buckets .addAll (generateBucketsForGeoPoint (geoPoint ));
170
+ }
171
+ cities .add (indexGeoPoints ("multi_valued_idx" , Integer .toString (i ), points ));
172
+ for (final String bucket : buckets ) {
173
+ multiValuedExpectedDocCountsGeoPoint .put (bucket , multiValuedExpectedDocCountsGeoPoint .getOrDefault (bucket , 0 ) + 1 );
174
+ }
175
+ }
176
+ indexRandom (true , cities );
177
+ ensureGreen ("multi_valued_idx" );
178
+ }
179
+
180
+ /**
181
+ * Returns a set of buckets for the GeoPoint at different precision level. Override this method for different bucket
182
+ * aggregations.
183
+ *
184
+ * @param geoPoint {@link GeoPoint}
185
+ * @return A {@link Set} of {@link String} which represents the buckets.
186
+ */
187
+ protected abstract Set <String > generateBucketsForGeoPoint (final GeoPoint geoPoint );
188
+
189
+ /**
190
+ * Indexes a GeoShape in the provided index.
191
+ * @param index {@link String} index name
192
+ * @param geometry {@link Geometry} the Geometry to be indexed
193
+ * @return {@link IndexRequestBuilder}
194
+ * @throws Exception thrown during creation of {@link IndexRequestBuilder}
195
+ */
55
196
protected IndexRequestBuilder indexGeoShape (final String index , final Geometry geometry ) throws Exception {
56
197
XContentBuilder source = jsonBuilder ().startObject ();
57
198
source = source .field (GEO_SHAPE_FIELD_NAME , WKT .toWKT (geometry ));
58
199
source = source .endObject ();
59
200
return client ().prepareIndex (index ).setSource (source );
60
201
}
61
202
62
- protected IndexRequestBuilder indexCity (final String index , final String name , final List <String > latLon ) throws Exception {
203
+ /**
204
+ * Indexes a {@link List} of {@link GeoPoint}s in the provided Index name.
205
+ * @param index {@link String} index name
206
+ * @param name {@link String} value for the string field in index
207
+ * @param latLon {@link List} of {@link String} representing the String representation of GeoPoint
208
+ * @return {@link IndexRequestBuilder}
209
+ * @throws Exception thrown during indexing.
210
+ */
211
+ protected IndexRequestBuilder indexGeoPoints (final String index , final String name , final List <String > latLon ) throws Exception {
63
212
XContentBuilder source = jsonBuilder ().startObject ().field (KEYWORD_FIELD_NAME , name );
64
213
if (latLon != null ) {
65
214
source = source .field (GEO_POINT_FIELD_NAME , latLon );
@@ -68,8 +217,38 @@ protected IndexRequestBuilder indexCity(final String index, final String name, f
68
217
return client ().prepareIndex (index ).setSource (source );
69
218
}
70
219
71
- protected IndexRequestBuilder indexCity (final String index , final String name , final String latLon ) throws Exception {
72
- return indexCity (index , name , List .of (latLon ));
220
+ /**
221
+ * Indexes a {@link GeoPoint} in the provided Index name.
222
+ * @param index {@link String} index name
223
+ * @param name {@link String} value for the string field in index
224
+ * @param latLon {@link String} representing the String representation of GeoPoint
225
+ * @return {@link IndexRequestBuilder}
226
+ * @throws Exception thrown during indexing.
227
+ */
228
+ protected IndexRequestBuilder indexGeoPoint (final String index , final String name , final String latLon ) throws Exception {
229
+ return indexGeoPoints (index , name , List .of (latLon ));
230
+ }
231
+
232
+ /**
233
+ * Generates a Bounding Box of a fixed radius that can be used for shapes aggregations to reduce the size of
234
+ * aggregation results.
235
+ * @param random {@link Random}
236
+ * @return {@link Rectangle}
237
+ */
238
+ protected Rectangle getGridAggregationBoundingBox (final Random random ) {
239
+ final double radius = getRadiusOfBoundingBox ();
240
+ assertTrue ("The radius of Bounding Box is less than or equal to 0" , radius > 0 );
241
+ return RandomGeoGeometryGenerator .randomRectangle (random , radius );
242
+ }
243
+
244
+ /**
245
+ * Returns a radius for the Bounding box. Test classes can override this method to change the radius of BBox for
246
+ * the test cases. If we increase this value, it will lead to creation of a lot of buckets that can lead of
247
+ * IndexOutOfBoundsExceptions.
248
+ * @return double
249
+ */
250
+ protected double getRadiusOfBoundingBox () {
251
+ return 5.0 ;
73
252
}
74
253
75
254
}
0 commit comments