/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.search.aggregations.metrics;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import com.carrotsearch.hppc.ObjectObjectHashMap;
import com.carrotsearch.hppc.ObjectObjectMap;
import org.elasticsearch.Version;
import org.apache.lucene.spatial.util.GeoHashUtils;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.test.geo.RandomGeoGenerator;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.hamcrest.Matchers.equalTo;
/**
*
*/
@ESIntegTestCase.SuiteScopeTestCase
public abstract class AbstractGeoTestCase extends ESIntegTestCase {
protected static final String SINGLE_VALUED_FIELD_NAME = "geo_value";
protected static final String MULTI_VALUED_FIELD_NAME = "geo_values";
protected static final String NUMBER_FIELD_NAME = "l_values";
protected static final String UNMAPPED_IDX_NAME = "idx_unmapped";
protected static final String IDX_NAME = "idx";
protected static final String EMPTY_IDX_NAME = "empty_idx";
protected static final String DATELINE_IDX_NAME = "dateline_idx";
protected static final String HIGH_CARD_IDX_NAME = "high_card_idx";
protected static final String IDX_ZERO_NAME = "idx_zero";
protected static int numDocs;
protected static int numUniqueGeoPoints;
protected static GeoPoint[] singleValues, multiValues;
protected static GeoPoint singleTopLeft, singleBottomRight, multiTopLeft, multiBottomRight, singleCentroid, multiCentroid, unmappedCentroid;
protected static ObjectIntMap<String> expectedDocCountsForGeoHash = null;
protected static ObjectObjectMap<String, GeoPoint> expectedCentroidsForGeoHash = null;
protected static final double GEOHASH_TOLERANCE = 1E-5D;
@Override
public void setupSuiteScopeCluster() throws Exception {
createIndex(UNMAPPED_IDX_NAME);
Version version = VersionUtils.randomVersionBetween(random(), Version.V_1_0_0, Version.CURRENT);
Settings settings = Settings.settingsBuilder().put(IndexMetaData.SETTING_VERSION_CREATED, version).build();
assertAcked(prepareCreate(IDX_NAME).setSettings(settings)
.addMapping("type", SINGLE_VALUED_FIELD_NAME, "type=geo_point,geohash_prefix=true,geohash_precision=12",
MULTI_VALUED_FIELD_NAME, "type=geo_point", NUMBER_FIELD_NAME, "type=long", "tag", "type=string,index=not_analyzed"));
singleTopLeft = new GeoPoint(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
singleBottomRight = new GeoPoint(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
multiTopLeft = new GeoPoint(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
multiBottomRight = new GeoPoint(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
singleCentroid = new GeoPoint(0, 0);
multiCentroid = new GeoPoint(0, 0);
unmappedCentroid = new GeoPoint(0, 0);
numDocs = randomIntBetween(6, 20);
numUniqueGeoPoints = randomIntBetween(1, numDocs);
expectedDocCountsForGeoHash = new ObjectIntHashMap<>(numDocs * 2);
expectedCentroidsForGeoHash = new ObjectObjectHashMap<>(numDocs * 2);
singleValues = new GeoPoint[numUniqueGeoPoints];
for (int i = 0 ; i < singleValues.length; i++)
{
singleValues[i] = RandomGeoGenerator.randomPoint(random());
updateBoundsTopLeft(singleValues[i], singleTopLeft);
updateBoundsBottomRight(singleValues[i], singleBottomRight);
}
multiValues = new GeoPoint[numUniqueGeoPoints];
for (int i = 0 ; i < multiValues.length; i++)
{
multiValues[i] = RandomGeoGenerator.randomPoint(random());
updateBoundsTopLeft(multiValues[i], multiTopLeft);
updateBoundsBottomRight(multiValues[i], multiBottomRight);
}
List<IndexRequestBuilder> builders = new ArrayList<>();
GeoPoint singleVal;
final GeoPoint[] multiVal = new GeoPoint[2];
double newMVLat, newMVLon;
for (int i = 0; i < numDocs; i++) {
singleVal = singleValues[i % numUniqueGeoPoints];
multiVal[0] = multiValues[i % numUniqueGeoPoints];
multiVal[1] = multiValues[(i+1) % numUniqueGeoPoints];
builders.add(client().prepareIndex(IDX_NAME, "type").setSource(jsonBuilder()
.startObject()
.array(SINGLE_VALUED_FIELD_NAME, singleVal.lon(), singleVal.lat())
.startArray(MULTI_VALUED_FIELD_NAME)
.startArray().value(multiVal[0].lon()).value(multiVal[0].lat()).endArray()
.startArray().value(multiVal[1].lon()).value(multiVal[1].lat()).endArray()
.endArray()
.field(NUMBER_FIELD_NAME, i)
.field("tag", "tag" + i)
.endObject()));
singleCentroid = singleCentroid.reset(singleCentroid.lat() + (singleVal.lat() - singleCentroid.lat()) / (i+1),
singleCentroid.lon() + (singleVal.lon() - singleCentroid.lon()) / (i+1));
newMVLat = (multiVal[0].lat() + multiVal[1].lat())/2d;
newMVLon = (multiVal[0].lon() + multiVal[1].lon())/2d;
multiCentroid = multiCentroid.reset(multiCentroid.lat() + (newMVLat - multiCentroid.lat()) / (i+1),
multiCentroid.lon() + (newMVLon - multiCentroid.lon()) / (i+1));
}
assertAcked(prepareCreate(EMPTY_IDX_NAME).setSettings(settings)
.addMapping("type", SINGLE_VALUED_FIELD_NAME, "type=geo_point"));
assertAcked(prepareCreate(DATELINE_IDX_NAME).setSettings(settings)
.addMapping("type", SINGLE_VALUED_FIELD_NAME, "type=geo_point", MULTI_VALUED_FIELD_NAME, "type=geo_point", NUMBER_FIELD_NAME, "type=long", "tag", "type=string,index=not_analyzed"));
GeoPoint[] geoValues = new GeoPoint[5];
geoValues[0] = new GeoPoint(38, 178);
geoValues[1] = new GeoPoint(12, -179);
geoValues[2] = new GeoPoint(-24, 170);
geoValues[3] = new GeoPoint(32, -175);
geoValues[4] = new GeoPoint(-11, 178);
for (int i = 0; i < 5; i++) {
builders.add(client().prepareIndex(DATELINE_IDX_NAME, "type").setSource(jsonBuilder()
.startObject()
.array(SINGLE_VALUED_FIELD_NAME, geoValues[i].lon(), geoValues[i].lat())
.field(NUMBER_FIELD_NAME, i)
.field("tag", "tag" + i)
.endObject()));
}
assertAcked(prepareCreate(HIGH_CARD_IDX_NAME)
.setSettings(Settings.builder().put("number_of_shards", 2).put(IndexMetaData.SETTING_VERSION_CREATED, version))
.addMapping("type", SINGLE_VALUED_FIELD_NAME, "type=geo_point", MULTI_VALUED_FIELD_NAME, "type=geo_point", NUMBER_FIELD_NAME, "type=long", "tag", "type=string,index=not_analyzed"));
for (int i = 0; i < 2000; i++) {
singleVal = singleValues[i % numUniqueGeoPoints];
builders.add(client().prepareIndex(HIGH_CARD_IDX_NAME, "type").setSource(jsonBuilder()
.startObject()
.array(SINGLE_VALUED_FIELD_NAME, singleVal.lon(), singleVal.lat())
.startArray(MULTI_VALUED_FIELD_NAME)
.startArray().value(multiValues[i % numUniqueGeoPoints].lon()).value(multiValues[i % numUniqueGeoPoints].lat()).endArray()
.startArray().value(multiValues[(i + 1) % numUniqueGeoPoints].lon()).value(multiValues[(i + 1) % numUniqueGeoPoints].lat()).endArray()
.endArray()
.field(NUMBER_FIELD_NAME, i)
.field("tag", "tag" + i)
.endObject()));
updateGeohashBucketsCentroid(singleVal);
}
builders.add(client().prepareIndex(IDX_ZERO_NAME, "type").setSource(
jsonBuilder().startObject().array(SINGLE_VALUED_FIELD_NAME, 0.0, 1.0).endObject()));
assertAcked(prepareCreate(IDX_ZERO_NAME).setSettings(settings)
.addMapping("type", SINGLE_VALUED_FIELD_NAME, "type=geo_point"));
indexRandom(true, builders);
ensureSearchable();
// Added to debug a test failure where the terms aggregation seems to be reporting two documents with the same value for NUMBER_FIELD_NAME. This will check that after
// random indexing each document only has 1 value for NUMBER_FIELD_NAME and it is the correct value. Following this initial change its seems that this call was getting
// more that 2000 hits (actual value was 2059) so now it will also check to ensure all hits have the correct index and type
SearchResponse response = client().prepareSearch(HIGH_CARD_IDX_NAME).addField(NUMBER_FIELD_NAME).addSort(SortBuilders.fieldSort(NUMBER_FIELD_NAME)
.order(SortOrder.ASC)).setSize(5000).get();
assertSearchResponse(response);
long totalHits = response.getHits().totalHits();
XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
response.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
logger.info("Full high_card_idx Response Content:\n{ {} }", builder.string());
for (int i = 0; i < totalHits; i++) {
SearchHit searchHit = response.getHits().getAt(i);
assertThat("Hit " + i + " with id: " + searchHit.getId(), searchHit.getIndex(), equalTo("high_card_idx"));
assertThat("Hit " + i + " with id: " + searchHit.getId(), searchHit.getType(), equalTo("type"));
SearchHitField hitField = searchHit.field(NUMBER_FIELD_NAME);
assertThat("Hit " + i + " has wrong number of values", hitField.getValues().size(), equalTo(1));
Integer value = hitField.getValue();
assertThat("Hit " + i + " has wrong value", value, equalTo(i));
}
assertThat(totalHits, equalTo(2000l));
}
private void updateGeohashBucketsCentroid(final GeoPoint location) {
String hash = GeoHashUtils.stringEncode(location.lon(), location.lat(), GeoHashUtils.PRECISION);
for (int precision = GeoHashUtils.PRECISION; precision > 0; --precision) {
final String h = hash.substring(0, precision);
expectedDocCountsForGeoHash.put(h, expectedDocCountsForGeoHash.getOrDefault(h, 0) + 1);
expectedCentroidsForGeoHash.put(h, updateHashCentroid(h, location));
}
}
private GeoPoint updateHashCentroid(String hash, final GeoPoint location) {
GeoPoint centroid = expectedCentroidsForGeoHash.getOrDefault(hash, null);
if (centroid == null) {
return new GeoPoint(location.lat(), location.lon());
}
final int docCount = expectedDocCountsForGeoHash.get(hash);
final double newLon = centroid.lon() + (location.lon() - centroid.lon()) / docCount;
final double newLat = centroid.lat() + (location.lat() - centroid.lat()) / docCount;
return centroid.reset(newLat, newLon);
}
private void updateBoundsBottomRight(GeoPoint geoPoint, GeoPoint currentBound) {
if (geoPoint.lat() < currentBound.lat()) {
currentBound.resetLat(geoPoint.lat());
}
if (geoPoint.lon() > currentBound.lon()) {
currentBound.resetLon(geoPoint.lon());
}
}
private void updateBoundsTopLeft(GeoPoint geoPoint, GeoPoint currentBound) {
if (geoPoint.lat() > currentBound.lat()) {
currentBound.resetLat(geoPoint.lat());
}
if (geoPoint.lon() < currentBound.lon()) {
currentBound.resetLon(geoPoint.lon());
}
}
}