org.elasticsearch.search.geo.GeoDistanceQueryBuilderTestCase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of framework Show documentation
Show all versions of framework Show documentation
Elasticsearch subproject :test:framework
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.search.geo;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.GeoValidationMethod;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.test.AbstractQueryTestCase;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
@SuppressWarnings("checkstyle:MissingJavadocMethod")
public abstract class GeoDistanceQueryBuilderTestCase extends AbstractQueryTestCase {
protected abstract String getFieldName();
@Override
protected GeoDistanceQueryBuilder doCreateTestQueryBuilder() {
String fieldName = getFieldName();
GeoDistanceQueryBuilder qb = new GeoDistanceQueryBuilder(fieldName);
String distance = "" + randomDouble();
if (randomBoolean()) {
DistanceUnit unit = randomFrom(DistanceUnit.values());
distance = distance + unit.toString();
}
int selector = randomIntBetween(0, 2);
switch (selector) {
case 0 -> qb.distance(randomDouble(), randomFrom(DistanceUnit.values()));
case 1 -> qb.distance(distance, randomFrom(DistanceUnit.values()));
case 2 -> qb.distance(distance);
}
qb.point(new GeoPoint(GeometryTestUtils.randomLat(), GeometryTestUtils.randomLon()));
if (randomBoolean()) {
qb.setValidationMethod(randomFrom(GeoValidationMethod.values()));
}
if (randomBoolean()) {
qb.geoDistance(randomFrom(GeoDistance.values()));
}
if (randomBoolean()) {
qb.ignoreUnmapped(randomBoolean());
}
return qb;
}
public void testIllegalValues() {
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new GeoDistanceQueryBuilder(""));
assertEquals("fieldName must not be null or empty", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> new GeoDistanceQueryBuilder((String) null));
assertEquals("fieldName must not be null or empty", e.getMessage());
GeoDistanceQueryBuilder query = new GeoDistanceQueryBuilder("fieldName");
e = expectThrows(IllegalArgumentException.class, () -> query.distance(""));
assertEquals("distance must not be null or empty", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> query.distance(null));
assertEquals("distance must not be null or empty", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> query.distance("", DistanceUnit.DEFAULT));
assertEquals("distance must not be null or empty", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> query.distance(null, DistanceUnit.DEFAULT));
assertEquals("distance must not be null or empty", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> query.distance("1", null));
assertEquals("distance unit must not be null", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> query.distance(1, null));
assertEquals("distance unit must not be null", e.getMessage());
e = expectThrows(
IllegalArgumentException.class,
() -> query.distance(randomIntBetween(Integer.MIN_VALUE, 0), DistanceUnit.DEFAULT)
);
assertEquals("distance must be greater than zero", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> query.geohash(null));
assertEquals("geohash must not be null or empty", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> query.geohash(""));
assertEquals("geohash must not be null or empty", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> query.geoDistance(null));
assertEquals("geoDistance must not be null", e.getMessage());
}
/**
* Overridden here to ensure the test is only run if at least one type is
* present in the mappings. Geo queries do not execute if the field is not
* explicitly mapped
*/
@Override
public void testToQuery() throws IOException {
super.testToQuery();
}
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/86834")
public void testParsingAndToQueryGeoJSON() throws IOException {
// TODO: GeoJSON support missing for geo_distance query, although all other point formats work
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"12mi",
"%s":{
"type": "Point",
"coordinates": [-70,40]
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 12, DistanceUnit.MILES);
}
public void testParsingAndToQueryWKT() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"12mi",
"%s":"POINT(-70 40)"
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 12, DistanceUnit.MILES);
}
public void testParsingAndToQuery1() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"12mi",
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 12, DistanceUnit.MILES);
}
public void testParsingAndToQuery2() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"12mi",
"%s":[-70, 40]
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 12, DistanceUnit.MILES);
}
public void testParsingAndToQuery3() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"12mi",
"%s":"40, -70"
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 12, DistanceUnit.MILES);
}
public void testParsingAndToQuery4() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"12mi",
"%s":"drn5x1g8cu2y"
}
}
""", GEO_POINT_FIELD_NAME);
GeoPoint geoPoint = GeoPoint.fromGeohash("drn5x1g8cu2y");
assertGeoDistanceRangeQuery(query, geoPoint.getLat(), geoPoint.getLon(), 12, DistanceUnit.MILES);
}
public void testParsingAndToQuery5() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":12,
"unit":"mi",
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 12, DistanceUnit.MILES);
}
public void testParsingAndToQuery6() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"12",
"unit":"mi",
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 12, DistanceUnit.MILES);
}
public void testParsingAndToQuery7() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"19.312128",
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 19.312128, DistanceUnit.DEFAULT);
}
public void testParsingAndToQuery8() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":19.312128,
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 19.312128, DistanceUnit.DEFAULT);
}
public void testParsingAndToQuery9() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"19.312128",
"unit":"km",
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 19.312128, DistanceUnit.KILOMETERS);
}
public void testParsingAndToQuery10() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":19.312128,
"unit":"km",
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 19.312128, DistanceUnit.KILOMETERS);
}
public void testParsingAndToQuery11() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"19.312128km",
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 19.312128, DistanceUnit.KILOMETERS);
}
public void testParsingAndToQuery12() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_distance":{
"distance":"12mi",
"unit":"km",
"%s":{
"lat":40,
"lon":-70
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoDistanceRangeQuery(query, 40, -70, 12, DistanceUnit.MILES);
}
private void assertGeoDistanceRangeQuery(String query, double lat, double lon, double distance, DistanceUnit distanceUnit)
throws IOException {
Query parsedQuery = parseQuery(query).toQuery(createSearchExecutionContext());
// The parsedQuery contains IndexOrDocValuesQuery, which wraps LatLonPointDistanceQuery which in turn has default visibility,
// so we cannot access its fields directly to check and have to use toString() here instead.
double qLat = GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(lat));
double qLon = GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lon));
assertThat(
parsedQuery.toString(),
containsString("mapped_geo_point:" + qLat + "," + qLon + " +/- " + distanceUnit.toMeters(distance) + " meters")
);
}
public void testFromJson() throws IOException {
String json = """
{
"geo_distance" : {
"pin.location" : [ -70.0, 40.0 ],
"distance" : 12000.0,
"distance_type" : "arc",
"validation_method" : "STRICT",
"ignore_unmapped" : false,
"boost" : 1.0
}
}""";
GeoDistanceQueryBuilder parsed = (GeoDistanceQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed);
assertEquals(json, -70.0, parsed.point().getLon(), 0.0001);
assertEquals(json, 40.0, parsed.point().getLat(), 0.0001);
assertEquals(json, 12000.0, parsed.distance(), 0.0001);
}
public void testIgnoreUnmapped() throws IOException {
final GeoDistanceQueryBuilder queryBuilder = new GeoDistanceQueryBuilder("unmapped").point(0.0, 0.0).distance("20m");
queryBuilder.ignoreUnmapped(true);
SearchExecutionContext searchExecutionContext = createSearchExecutionContext();
Query query = queryBuilder.toQuery(searchExecutionContext);
assertThat(query, notNullValue());
assertThat(query, instanceOf(MatchNoDocsQuery.class));
final GeoDistanceQueryBuilder failingQueryBuilder = new GeoDistanceQueryBuilder("unmapped").point(0.0, 0.0).distance("20m");
failingQueryBuilder.ignoreUnmapped(false);
QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(searchExecutionContext));
assertThat(e.getMessage(), containsString("failed to find geo field [unmapped]"));
}
public void testParseFailsWithMultipleFields() throws IOException {
String json = """
{
"geo_distance" : {
"point1" : {
"lat" : 30, "lon" : 12
},
"point2" : {
"lat" : 30, "lon" : 12
}
}
}""";
ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(json));
assertEquals("[geo_distance] query doesn't support multiple fields, found [point1] and [point2]", e.getMessage());
}
}