org.elasticsearch.search.geo.GeoBoundingBoxQueryBuilderTestCase 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 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 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.search.geo;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.Geohash;
import org.elasticsearch.index.query.GeoBoundingBoxQueryBuilder;
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 GeoBoundingBoxQueryBuilderTestCase extends AbstractQueryTestCase {
/** Randomly generate either NaN or one of the two infinity values. */
private static final Double[] brokenDoubles = { Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY };
protected abstract String getFieldName();
@Override
protected GeoBoundingBoxQueryBuilder doCreateTestQueryBuilder() {
String fieldName = getFieldName();
GeoBoundingBoxQueryBuilder builder = new GeoBoundingBoxQueryBuilder(fieldName);
// make sure that minX != maxX and minY != maxY after geohash encoding
Rectangle box = randomValueOtherThanMany(
(r) -> Math.abs(r.getMaxX() - r.getMinX()) < 0.1 || Math.abs(r.getMaxY() - r.getMinY()) < 0.1,
GeometryTestUtils::randomRectangle
);
if (randomBoolean()) {
// check the top-left/bottom-right combination of setters
int path = randomIntBetween(0, 2);
switch (path) {
case 0 -> builder.setCorners(new GeoPoint(box.getMaxY(), box.getMinX()), new GeoPoint(box.getMinY(), box.getMaxX()));
case 1 -> builder.setCorners(
Geohash.stringEncode(box.getMinX(), box.getMaxY()),
Geohash.stringEncode(box.getMaxX(), box.getMinY())
);
default -> builder.setCorners(box.getMaxY(), box.getMinX(), box.getMinY(), box.getMaxX());
}
} else {
// check the bottom-left/ top-right combination of setters
if (randomBoolean()) {
builder.setCornersOGC(new GeoPoint(box.getMinY(), box.getMinX()), new GeoPoint(box.getMaxY(), box.getMaxX()));
} else {
builder.setCornersOGC(
Geohash.stringEncode(box.getMinX(), box.getMinY()),
Geohash.stringEncode(box.getMaxX(), box.getMaxY())
);
}
}
if (randomBoolean()) {
builder.setValidationMethod(randomFrom(GeoValidationMethod.values()));
}
if (randomBoolean()) {
builder.ignoreUnmapped(randomBoolean());
}
return builder;
}
public void testValidationNullFieldname() {
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new GeoBoundingBoxQueryBuilder((String) null));
assertEquals("Field name must not be empty.", e.getMessage());
}
public void testExceptionOnMissingTypes() {
SearchExecutionContext context = createShardContextWithNoType();
GeoBoundingBoxQueryBuilder qb = createTestQueryBuilder();
qb.ignoreUnmapped(false);
QueryShardException e = expectThrows(QueryShardException.class, () -> qb.toQuery(context));
assertEquals("failed to find geo field [" + qb.fieldName() + "]", e.getMessage());
}
public void testBrokenCoordinateCannotBeSet() {
PointTester[] testers = { new TopTester(), new LeftTester(), new BottomTester(), new RightTester() };
GeoBoundingBoxQueryBuilder builder = createTestQueryBuilder();
builder.setValidationMethod(GeoValidationMethod.STRICT);
for (PointTester tester : testers) {
expectThrows(IllegalArgumentException.class, () -> tester.invalidateCoordinate(builder, true));
}
}
public void testBrokenCoordinateCanBeSetWithIgnoreMalformed() {
PointTester[] testers = { new TopTester(), new LeftTester(), new BottomTester(), new RightTester() };
GeoBoundingBoxQueryBuilder builder = createTestQueryBuilder();
builder.setValidationMethod(GeoValidationMethod.IGNORE_MALFORMED);
for (PointTester tester : testers) {
tester.invalidateCoordinate(builder, true);
}
}
public void testTopBottomCannotBeFlipped() {
GeoBoundingBoxQueryBuilder builder = createTestQueryBuilder();
double top = builder.topLeft().getLat();
double left = builder.topLeft().getLon();
double bottom = builder.bottomRight().getLat();
double right = builder.bottomRight().getLon();
assumeTrue("top should not be equal to bottom for flip check", top != bottom);
logger.info("top: {} bottom: {}", top, bottom);
builder.setValidationMethod(GeoValidationMethod.STRICT);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> builder.setCorners(bottom, left, top, right));
assertThat(e.getMessage(), containsString("top is below bottom corner:"));
}
public void testTopBottomCanBeFlippedOnIgnoreMalformed() {
GeoBoundingBoxQueryBuilder builder = createTestQueryBuilder();
double top = builder.topLeft().getLat();
double left = builder.topLeft().getLon();
double bottom = builder.bottomRight().getLat();
double right = builder.bottomRight().getLon();
assumeTrue("top should not be equal to bottom for flip check", top != bottom);
builder.setValidationMethod(GeoValidationMethod.IGNORE_MALFORMED).setCorners(bottom, left, top, right);
}
public void testLeftRightCanBeFlipped() {
GeoBoundingBoxQueryBuilder builder = createTestQueryBuilder();
double top = builder.topLeft().getLat();
double left = builder.topLeft().getLon();
double bottom = builder.bottomRight().getLat();
double right = builder.bottomRight().getLon();
builder.setValidationMethod(GeoValidationMethod.IGNORE_MALFORMED).setCorners(top, right, bottom, left);
builder.setValidationMethod(GeoValidationMethod.STRICT).setCorners(top, right, bottom, left);
}
public void testTopAndBottomCanBeEqual() {
GeoBoundingBoxQueryBuilder builder = createTestQueryBuilder();
double topBottom = builder.topLeft().getLat();
double left = builder.topLeft().getLon();
double right = builder.bottomRight().getLon();
builder.setValidationMethod(GeoValidationMethod.STRICT).setCorners(topBottom, left, topBottom, right);
builder.setValidationMethod(GeoValidationMethod.IGNORE_MALFORMED).setCorners(topBottom, left, topBottom, right);
}
public void testLeftAndRightCanBeEqual() {
GeoBoundingBoxQueryBuilder builder = createTestQueryBuilder();
double top = builder.topLeft().getLat();
double leftRight = builder.topLeft().getLon();
double bottom = builder.bottomRight().getLat();
builder.setValidationMethod(GeoValidationMethod.STRICT).setCorners(top, leftRight, bottom, leftRight);
builder.setValidationMethod(GeoValidationMethod.IGNORE_MALFORMED).setCorners(top, leftRight, bottom, leftRight);
}
public void testTopBottomAndLeftRightCanBeEqual() {
GeoBoundingBoxQueryBuilder builder = createTestQueryBuilder();
double topBottom = builder.topLeft().getLat();
double leftRight = builder.topLeft().getLon();
builder.setValidationMethod(GeoValidationMethod.STRICT).setCorners(topBottom, leftRight, topBottom, leftRight);
builder.setValidationMethod(GeoValidationMethod.IGNORE_MALFORMED).setCorners(topBottom, leftRight, topBottom, leftRight);
}
public void testStrictnessDefault() {
assertFalse(
"Someone changed the default for coordinate validation - were the docs changed as well?",
GeoValidationMethod.DEFAULT_LENIENT_PARSING
);
}
protected abstract static class PointTester {
private final double brokenCoordinate = randomFrom(brokenDoubles);
public final double invalidCoordinate;
protected PointTester(double invalidCoodinate) {
this.invalidCoordinate = invalidCoodinate;
}
public void invalidateCoordinate(GeoBoundingBoxQueryBuilder qb, boolean useBrokenDouble) {
if (useBrokenDouble) {
fillIn(brokenCoordinate, qb);
} else {
fillIn(invalidCoordinate, qb);
}
}
protected abstract void fillIn(double fillIn, GeoBoundingBoxQueryBuilder qb);
}
protected static class TopTester extends PointTester {
public TopTester() {
super(randomDoubleBetween(GeoUtils.MAX_LAT, Double.MAX_VALUE, false));
}
@Override
public void fillIn(double coordinate, GeoBoundingBoxQueryBuilder qb) {
qb.setCorners(coordinate, qb.topLeft().getLon(), qb.bottomRight().getLat(), qb.bottomRight().getLon());
}
}
protected static class LeftTester extends PointTester {
public LeftTester() {
super(randomDoubleBetween(-Double.MAX_VALUE, GeoUtils.MIN_LON, true));
}
@Override
public void fillIn(double coordinate, GeoBoundingBoxQueryBuilder qb) {
qb.setCorners(qb.topLeft().getLat(), coordinate, qb.bottomRight().getLat(), qb.bottomRight().getLon());
}
}
protected static class BottomTester extends PointTester {
public BottomTester() {
super(randomDoubleBetween(-Double.MAX_VALUE, GeoUtils.MIN_LAT, false));
}
@Override
public void fillIn(double coordinate, GeoBoundingBoxQueryBuilder qb) {
qb.setCorners(qb.topLeft().getLat(), qb.topLeft().getLon(), coordinate, qb.bottomRight().getLon());
}
}
protected static class RightTester extends PointTester {
public RightTester() {
super(randomDoubleBetween(GeoUtils.MAX_LON, Double.MAX_VALUE, true));
}
@Override
public void fillIn(double coordinate, GeoBoundingBoxQueryBuilder qb) {
qb.setCorners(qb.topLeft().getLat(), qb.topLeft().getLon(), qb.bottomRight().getLat(), coordinate);
}
}
public void testParsingAndToQueryGeoJSON() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box":{
"%s":{
"top_left":{
"type":"Point",
"coordinates":[-70, 40]
},
"bottom_right":{
"type":"Point",
"coordinates":[-80, 30]
}
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
public void testParsingAndToQueryWKT() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box":{
"%s":{
"top_left":"POINT(-70 40)",
"bottom_right":"POINT(-80 30)"
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
public void testParsingAndToQuery1() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box":{
"%s":{
"top_left":[-70, 40],
"bottom_right":[-80, 30]
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
public void testParsingAndToQuery2() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box":{
"%s":{
"top_left":{
"lat":40,
"lon":-70
},
"bottom_right":{
"lat":30,
"lon":-80
}
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
public void testParsingAndToQuery3() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box":{
"%s":{
"top_left":"40, -70",
"bottom_right":"30, -80"
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
public void testParsingAndToQuery4() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box":{
"%s":{
"top_left":"drn5x1g8cu2y",
"bottom_right":"30, -80"
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
public void testParsingAndToQuery5() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box":{
"%s":{
"top_right":"40, -80",
"bottom_left":"30, -70"
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
public void testParsingAndToQuery6() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box":{
"%s":{
"right": -80,
"top": 40,
"left": -70,
"bottom": 30
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
private void assertGeoBoundingBoxQuery(String query) throws IOException {
SearchExecutionContext searchExecutionContext = createSearchExecutionContext();
// just check if we can parse the query
parseQuery(query).toQuery(searchExecutionContext);
}
public void testFromJson() throws IOException {
String json = """
{
"geo_bounding_box" : {
"pin.location" : {
"top_left" : [ -74.1, 40.73 ],
"bottom_right" : [ -71.12, 40.01 ]
},
"validation_method" : "STRICT",
"ignore_unmapped" : false,
"boost" : 1.0
}
}""";
GeoBoundingBoxQueryBuilder parsed = (GeoBoundingBoxQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed);
assertEquals(json, "pin.location", parsed.fieldName());
assertEquals(json, -74.1, parsed.topLeft().getLon(), 0.0001);
assertEquals(json, 40.73, parsed.topLeft().getLat(), 0.0001);
assertEquals(json, -71.12, parsed.bottomRight().getLon(), 0.0001);
assertEquals(json, 40.01, parsed.bottomRight().getLat(), 0.0001);
assertEquals(json, 1.0, parsed.boost(), 0.0001);
}
public void testFromWKT() throws IOException {
String wkt = """
{
"geo_bounding_box" : {
"pin.location" : {
"wkt" : "BBOX (-74.1, -71.12, 40.73, 40.01)"
},
"validation_method" : "STRICT",
"ignore_unmapped" : false,
"boost" : 1.0
}
}""";
// toXContent generates the query in geojson only; for now we need to test against the expected
// geojson generated content
String expectedJson = """
{
"geo_bounding_box" : {
"pin.location" : {
"top_left" : [ -74.1, 40.73 ],
"bottom_right" : [ -71.12, 40.01 ]
},
"validation_method" : "STRICT",
"ignore_unmapped" : false,
"boost" : 1.0
}
}""";
// parse with wkt
GeoBoundingBoxQueryBuilder parsed = (GeoBoundingBoxQueryBuilder) parseQuery(wkt);
// check the builder's generated geojson content against the expected json output
checkGeneratedJson(expectedJson, parsed);
double delta = 0d;
assertEquals(expectedJson, "pin.location", parsed.fieldName());
assertEquals(expectedJson, -74.1, parsed.topLeft().getLon(), delta);
assertEquals(expectedJson, 40.73, parsed.topLeft().getLat(), delta);
assertEquals(expectedJson, -71.12, parsed.bottomRight().getLon(), delta);
assertEquals(expectedJson, 40.01, parsed.bottomRight().getLat(), delta);
assertEquals(expectedJson, 1.0, parsed.boost(), delta);
}
public void testFromGeohash() throws IOException {
String json = """
{
"geo_bounding_box" : {
"pin.location" : {
"top_left" : "dr",
"bottom_right" : "dq"
},
"validation_method" : "STRICT",
"ignore_unmapped" : false,
"boost" : 1.0
}
}""";
String expectedJson = """
{
"geo_bounding_box" : {
"pin.location" : {
"top_left" : [ -78.75, 45.0 ],
"bottom_right" : [ -67.5, 33.75 ]
},
"validation_method" : "STRICT",
"ignore_unmapped" : false,
"boost" : 1.0
}
}""";
GeoBoundingBoxQueryBuilder parsed = (GeoBoundingBoxQueryBuilder) parseQuery(json);
checkGeneratedJson(expectedJson, parsed);
assertEquals(json, "pin.location", parsed.fieldName());
assertEquals(json, -78.75, parsed.topLeft().getLon(), 0.0001);
assertEquals(json, 45.0, parsed.topLeft().getLat(), 0.0001);
assertEquals(json, -67.5, parsed.bottomRight().getLon(), 0.0001);
assertEquals(json, 33.75, parsed.bottomRight().getLat(), 0.0001);
assertEquals(json, 1.0, parsed.boost(), 0.0001);
}
public void testMalformedGeohashes() {
String jsonGeohashAndWkt = """
{
"geo_bounding_box" : {
"pin.location" : {
"top_left" : [ -78.75, 45.0 ],
"wkt" : "BBOX (-74.1, -71.12, 40.73, 40.01)"
},
"validation_method" : "STRICT",
"ignore_unmapped" : false,
"boost" : 1.0
}
}""";
ElasticsearchParseException e1 = expectThrows(ElasticsearchParseException.class, () -> parseQuery(jsonGeohashAndWkt));
assertThat(e1.getMessage(), containsString("Conflicting definition found using well-known text and explicit corners."));
}
public void testHonorsCoercion() throws IOException {
String query = String.format(java.util.Locale.ROOT, """
{
"geo_bounding_box": {
"validation_method": "COERCE",
"%s": {
"top_left": {
"lat": -15.5,
"lon": 176.5
},
"bottom_right": {
"lat": -19.6,
"lon": 181
}
}
}
}
""", GEO_POINT_FIELD_NAME);
assertGeoBoundingBoxQuery(query);
}
@Override
public void testMustRewrite() throws IOException {
super.testMustRewrite();
}
public void testIgnoreUnmapped() throws IOException {
final GeoBoundingBoxQueryBuilder queryBuilder = new GeoBoundingBoxQueryBuilder("unmapped").setCorners(1.0, 0.0, 0.0, 1.0);
queryBuilder.ignoreUnmapped(true);
SearchExecutionContext searchExecutionContext = createSearchExecutionContext();
Query query = queryBuilder.toQuery(searchExecutionContext);
assertThat(query, notNullValue());
assertThat(query, instanceOf(MatchNoDocsQuery.class));
final GeoBoundingBoxQueryBuilder failingQueryBuilder = new GeoBoundingBoxQueryBuilder("unmapped").setCorners(1.0, 0.0, 0.0, 1.0);
failingQueryBuilder.ignoreUnmapped(false);
QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(searchExecutionContext));
assertThat(e.getMessage(), containsString("failed to find geo field [unmapped]"));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy