diff --git a/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java index 59025dc03e161..4115cc4b7671c 100644 --- a/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java @@ -21,6 +21,7 @@ import org.apache.lucene.document.LatLonDocValuesField; import org.apache.lucene.document.LatLonPoint; +import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.IndexOrDocValuesQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; @@ -28,20 +29,28 @@ import org.elasticsearch.common.Numbers; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; -import org.elasticsearch.common.geo.GeoBoundingBox; -import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.common.geo.*; +import org.elasticsearch.common.geo.builders.ShapeBuilder; +import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.geometry.utils.Geohash; +import org.elasticsearch.index.mapper.AbstractSearchableGeometryFieldType; +import org.elasticsearch.index.mapper.GeoPointFieldMapper; import org.elasticsearch.index.mapper.GeoPointFieldMapper.GeoPointFieldType; +import org.elasticsearch.index.mapper.GeoShapeFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Objects; +import java.util.function.Supplier; /** * Creates a Lucene query that will filter for all documents that lie within the specified @@ -50,7 +59,7 @@ * This query can only operate on fields of type geo_point that have latitude and longitude * enabled. * */ -public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder { +public class GeoBoundingBoxQueryBuilder extends AbstractGeometryQueryBuilder { public static final String NAME = "geo_bounding_box"; /** Default type for executing this query (memory as of this writing). */ @@ -67,7 +76,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder validContentTypes = + Collections.unmodifiableList( + Arrays.asList( + GeoShapeFieldMapper.CONTENT_TYPE, + GeoPointFieldMapper.CONTENT_TYPE)); + + protected List validContentTypes() { + return validContentTypes; + } /** * Create new bounding box query. * @param fieldName name of index field containing geo coordinates to operate on. * */ - public GeoBoundingBoxQueryBuilder(String fieldName) { - if (fieldName == null) { - throw new IllegalArgumentException("Field name must not be empty."); - } - this.fieldName = fieldName; +// public GeoBoundingBoxQueryBuilder(String fieldName) { +// if (fieldName == null) { +// throw new IllegalArgumentException("Field name must not be empty."); +// } +// this.fieldName = fieldName; +// } + + public GeoBoundingBoxQueryBuilder(String fieldName, Geometry shape){ + super(fieldName, shape); } + + public GeoBoundingBoxQueryBuilder(String fieldName, Supplier shapeSupplier, String indexedShapeId) { + super(fieldName, shapeSupplier, indexedShapeId); + } /** * Read from a stream. */ public GeoBoundingBoxQueryBuilder(StreamInput in) throws IOException { super(in); - fieldName = in.readString(); +// fieldName = in.readString(); geoBoundingBox = new GeoBoundingBox(in); type = GeoExecType.readFromStream(in); validationMethod = GeoValidationMethod.readFromStream(in); ignoreUnmapped = in.readBoolean(); + strategy = in.readOptionalWriteable(SpatialStrategy::readFromStream); + } + @Deprecated + public GeoBoundingBoxQueryBuilder(String fieldName, ShapeBuilder shape) { + super(fieldName, shape); + } + + public GeoBoundingBoxQueryBuilder(String fieldName, String indexedShapeId){ + super(fieldName, indexedShapeId); } @Override @@ -302,20 +339,66 @@ QueryValidationException checkLatLon() { return validationException; } +// @Override +// public Query doToQuery(QueryShardContext context) { +// MappedFieldType fieldType = context.fieldMapper(fieldName); +// if (fieldType == null) { +// if (ignoreUnmapped) { +// return new MatchNoDocsQuery(); +// } else { +// throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]"); +// } +// } +//// if (!(fieldType instanceof GeoPointFieldType)) { +//// throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field"); +//// } +// if (!validContentTypes().contains(fieldType.typeName())) { +// throw new QueryShardException(context, +// "Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "]. [" +// + NAME + "] query supports the following types [" +// + String.join(",", validContentTypes()) + "]"); +// } +// QueryValidationException exception = checkLatLon(); +// if (exception != null) { +// throw new QueryShardException(context, "couldn't validate latitude/ longitude values", exception); +// } +// +// GeoPoint luceneTopLeft = new GeoPoint(geoBoundingBox.topLeft()); +// GeoPoint luceneBottomRight = new GeoPoint(geoBoundingBox.bottomRight()); +// if (GeoValidationMethod.isCoerce(validationMethod)) { +// // Special case: if the difference between the left and right is 360 and the right is greater than the left, we are asking for +// // the complete longitude range so need to set longitude to the complete longitude range +// double right = luceneBottomRight.getLon(); +// double left = luceneTopLeft.getLon(); +// +// boolean completeLonRange = ((right - left) % 360 == 0 && right > left); +// GeoUtils.normalizePoint(luceneTopLeft, true, !completeLonRange); +// GeoUtils.normalizePoint(luceneBottomRight, true, !completeLonRange); +// if (completeLonRange) { +// luceneTopLeft.resetLon(-180); +// luceneBottomRight.resetLon(180); +// } +// } +// +// Query query = LatLonPoint.newBoxQuery(fieldType.name(), luceneBottomRight.getLat(), luceneTopLeft.getLat(), +// luceneTopLeft.getLon(), luceneBottomRight.getLon()); +// if (fieldType.hasDocValues()) { +// Query dvQuery = LatLonDocValuesField.newSlowBoxQuery(fieldType.name(), +// luceneBottomRight.getLat(), luceneTopLeft.getLat(), +// luceneTopLeft.getLon(), luceneBottomRight.getLon()); +// query = new IndexOrDocValuesQuery(query, dvQuery); +// } +// return query; +// } + @Override - public Query doToQuery(QueryShardContext context) { - MappedFieldType fieldType = context.fieldMapper(fieldName); - if (fieldType == null) { - if (ignoreUnmapped) { - return new MatchNoDocsQuery(); - } else { - throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]"); - } - } - if (!(fieldType instanceof GeoPointFieldType)) { - throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field"); + public Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType) { + if (!validContentTypes().contains(fieldType.typeName())) { + throw new QueryShardException(context, + "Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "]. [" + + NAME + "] query supports the following types [" + + String.join(",", validContentTypes()) + "]"); } - QueryValidationException exception = checkLatLon(); if (exception != null) { throw new QueryShardException(context, "couldn't validate latitude/ longitude values", exception); @@ -337,16 +420,26 @@ public Query doToQuery(QueryShardContext context) { luceneBottomRight.resetLon(180); } } + final AbstractSearchableGeometryFieldType ft = + (AbstractSearchableGeometryFieldType) fieldType; + return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, fieldName, strategy, relation, context)); + } - Query query = LatLonPoint.newBoxQuery(fieldType.name(), luceneBottomRight.getLat(), luceneTopLeft.getLat(), - luceneTopLeft.getLon(), luceneBottomRight.getLon()); - if (fieldType.hasDocValues()) { - Query dvQuery = LatLonDocValuesField.newSlowBoxQuery(fieldType.name(), - luceneBottomRight.getLat(), luceneTopLeft.getLat(), - luceneTopLeft.getLon(), luceneBottomRight.getLon()); - query = new IndexOrDocValuesQuery(query, dvQuery); + @Override + protected void doShapeQueryXContent(XContentBuilder builder, Params params) throws IOException { + if (strategy != null) { + builder.field(STRATEGY_FIELD.getPreferredName(), strategy.getStrategyName()); } - return query; + } + + @Override + protected GeoBoundingBoxQueryBuilder newShapeQueryBuilder(String fieldName, Geometry shape) { + return new GeoBoundingBoxQueryBuilder(fieldName, shape); + } + + @Override + protected GeoBoundingBoxQueryBuilder newShapeQueryBuilder(String fieldName, Supplier shapeSupplier, String indexedShapeId) { + return new GeoBoundingBoxQueryBuilder(fieldName, shapeSupplier, indexedShapeId); } @Override @@ -365,62 +458,133 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep builder.endObject(); } - public static GeoBoundingBoxQueryBuilder fromXContent(XContentParser parser) throws IOException { - String fieldName = null; - - float boost = AbstractQueryBuilder.DEFAULT_BOOST; - String queryName = null; - String currentFieldName = null; - XContentParser.Token token; - GeoValidationMethod validationMethod = null; - boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED; - - // bottom (minLat), top (maxLat), left (minLon), right (maxLon) - GeoBoundingBox bbox = null; - String type = "memory"; - - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token == XContentParser.Token.START_OBJECT) { - try { - bbox = GeoBoundingBox.parseBoundingBox(parser); - fieldName = currentFieldName; - } catch (Exception e) { - throw new ElasticsearchParseException("failed to parse [{}] query. [{}]", NAME, e.getMessage()); - } - } else if (token.isValue()) { - if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - queryName = parser.text(); - } else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - boost = parser.floatValue(); - } else if (VALIDATION_METHOD_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - validationMethod = GeoValidationMethod.fromString(parser.text()); - } else if (IGNORE_UNMAPPED_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - ignoreUnmapped = parser.booleanValue(); - } else if (TYPE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - type = parser.text(); +// public static GeoBoundingBoxQueryBuilder fromXContent(XContentParser parser) throws IOException { +// String fieldName = null; +// +// float boost = AbstractQueryBuilder.DEFAULT_BOOST; +// String queryName = null; +// String currentFieldName = null; +// XContentParser.Token token; +// GeoValidationMethod validationMethod = null; +// boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED; +// +// // bottom (minLat), top (maxLat), left (minLon), right (maxLon) +// GeoBoundingBox bbox = null; +// String type = "memory"; +// +// while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { +// if (token == XContentParser.Token.FIELD_NAME) { +// currentFieldName = parser.currentName(); +// } else if (token == XContentParser.Token.START_OBJECT) { +// try { +// bbox = GeoBoundingBox.parseBoundingBox(parser); +// fieldName = currentFieldName; +// } catch (Exception e) { +// throw new ElasticsearchParseException("failed to parse [{}] query. [{}]", NAME, e.getMessage()); +// } +// } else if (token.isValue()) { +// if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// queryName = parser.text(); +// } else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// boost = parser.floatValue(); +// } else if (VALIDATION_METHOD_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// validationMethod = GeoValidationMethod.fromString(parser.text()); +// } else if (IGNORE_UNMAPPED_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// ignoreUnmapped = parser.booleanValue(); +// } else if (TYPE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// type = parser.text(); +// } else { +// throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. unexpected field [{}]", +// NAME, currentFieldName); +// } +// } +// } +// +// if (bbox == null) { +// throw new ElasticsearchParseException("failed to parse [{}] query. bounding box not provided", NAME); +// } +// +// GeoBoundingBoxQueryBuilder builder = new GeoBoundingBoxQueryBuilder(fieldName, ); +// builder.setCorners(bbox.topLeft(), bbox.bottomRight()); +// builder.queryName(queryName); +// builder.boost(boost); +// builder.type(GeoExecType.fromString(type)); +// builder.ignoreUnmapped(ignoreUnmapped); +// if (validationMethod != null) { +// // ignore deprecated coerce/ignoreMalformed settings if validationMethod is set +// builder.setValidationMethod(validationMethod); +// } +// return builder; +// } + public GeoBoundingBoxQueryBuilder strategy(SpatialStrategy strategy) { + if (strategy != null && strategy == SpatialStrategy.TERM && relation != ShapeRelation.INTERSECTS) { + throw new IllegalArgumentException("strategy [" + strategy.getStrategyName() + "] only supports relation [" + + ShapeRelation.INTERSECTS.getRelationName() + "] found relation [" + relation.getRelationName() + "]"); + } + this.strategy = strategy; + return this; + } + + private static class ParsedGeoShapeQueryParams extends ParsedGeometryQueryParams { + SpatialStrategy strategy; + + @Override + protected boolean parseXContentField(XContentParser parser) throws IOException { + SpatialStrategy strategy; + if (SHAPE_FIELD.match(parser.currentName(), parser.getDeprecationHandler())) { + this.shape = ShapeParser.parse(parser); + return true; + } else if (STRATEGY_FIELD.match(parser.currentName(), parser.getDeprecationHandler())) { + String strategyName = parser.text(); + strategy = SpatialStrategy.fromString(strategyName); + if (strategy == null) { + throw new ParsingException(parser.getTokenLocation(), "Unknown strategy [" + strategyName + " ]"); } else { - throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. unexpected field [{}]", - NAME, currentFieldName); + this.strategy = strategy; } + return true; } + return false; + } + } + public static GeoBoundingBoxQueryBuilder fromXContent(XContentParser parser) throws IOException { + ParsedGeoShapeQueryParams pgsqp = + (ParsedGeoShapeQueryParams) AbstractGeometryQueryBuilder.parsedParamsFromXContent(parser, new ParsedGeoShapeQueryParams()); + + GeoBoundingBoxQueryBuilder builder; + + if (pgsqp.shape != null) { + builder = new GeoBoundingBoxQueryBuilder(pgsqp.fieldName, pgsqp.shape); + } else { + builder = new GeoBoundingBoxQueryBuilder(pgsqp.fieldName, pgsqp.id); } - if (bbox == null) { - throw new ElasticsearchParseException("failed to parse [{}] query. bounding box not provided", NAME); + if (pgsqp.index != null) { + builder.indexedShapeIndex(pgsqp.index); } - GeoBoundingBoxQueryBuilder builder = new GeoBoundingBoxQueryBuilder(fieldName); - builder.setCorners(bbox.topLeft(), bbox.bottomRight()); - builder.queryName(queryName); - builder.boost(boost); - builder.type(GeoExecType.fromString(type)); - builder.ignoreUnmapped(ignoreUnmapped); - if (validationMethod != null) { - // ignore deprecated coerce/ignoreMalformed settings if validationMethod is set - builder.setValidationMethod(validationMethod); + if (pgsqp.shapePath != null) { + builder.indexedShapePath(pgsqp.shapePath); } + + if (pgsqp.shapeRouting != null) { + builder.indexedShapeRouting(pgsqp.shapeRouting); + } + + if (pgsqp.relation != null) { + builder.relation(pgsqp.relation); + } + + if (pgsqp.strategy != null) { + builder.strategy(pgsqp.strategy); + } + + if (pgsqp.queryName != null) { + builder.queryName(pgsqp.queryName); + } + + builder.boost(pgsqp.boost); + builder.ignoreUnmapped(pgsqp.ignoreUnmapped); return builder; } diff --git a/server/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java index 5db7516437314..2fcf4f5bb2718 100644 --- a/server/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java @@ -21,32 +21,37 @@ import org.apache.lucene.document.LatLonDocValuesField; import org.apache.lucene.document.LatLonPoint; +import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.IndexOrDocValuesQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.geo.GeoDistance; -import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.common.geo.*; +import org.elasticsearch.common.geo.builders.ShapeBuilder; +import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.index.mapper.AbstractSearchableGeometryFieldType; +import org.elasticsearch.index.mapper.GeoPointFieldMapper; import org.elasticsearch.index.mapper.GeoPointFieldMapper.GeoPointFieldType; +import org.elasticsearch.index.mapper.GeoShapeFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; import java.io.IOException; -import java.util.Locale; -import java.util.Objects; +import java.util.*; +import java.util.function.Supplier; /** * Filter results of a query to include only those within a specific distance to some * geo point. */ -public class GeoDistanceQueryBuilder extends AbstractQueryBuilder { +public class GeoDistanceQueryBuilder extends AbstractGeometryQueryBuilder { public static final String NAME = "geo_distance"; /** Default for distance unit computation. */ @@ -65,7 +70,7 @@ public class GeoDistanceQueryBuilder extends AbstractQueryBuilder validContentTypes = + Collections.unmodifiableList( + Arrays.asList( + GeoShapeFieldMapper.CONTENT_TYPE, + GeoPointFieldMapper.CONTENT_TYPE)); + + protected List validContentTypes() { + return validContentTypes; + } /** * Construct new GeoDistanceQueryBuilder. * @param fieldName name of indexed geo field to operate distance computation on. * */ - public GeoDistanceQueryBuilder(String fieldName) { - if (Strings.isEmpty(fieldName)) { - throw new IllegalArgumentException("fieldName must not be null or empty"); - } - this.fieldName = fieldName; + public GeoDistanceQueryBuilder(String fieldName, String indexedShapeId) { + super(fieldName, indexedShapeId); + } + public GeoDistanceQueryBuilder(String fieldName, Geometry geometry){ + super(fieldName, geometry); } + public GeoDistanceQueryBuilder(String fieldName, Supplier geometrySupplier ,String indexedShapeId){ + super(fieldName, geometrySupplier, indexedShapeId); + } + public GeoDistanceQueryBuilder(String fieldName, ShapeBuilder shapeBuilder) { + super(fieldName, shapeBuilder); + } /** * Read from a stream. */ public GeoDistanceQueryBuilder(StreamInput in) throws IOException { super(in); - fieldName = in.readString(); +// fieldName = in.readString(); distance = in.readDouble(); validationMethod = GeoValidationMethod.readFromStream(in); center = in.readGeoPoint(); geoDistance = GeoDistance.readFromStream(in); ignoreUnmapped = in.readBoolean(); + strategy = in.readOptionalWriteable(SpatialStrategy::readFromStream); } @Override @@ -111,6 +134,15 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeBoolean(ignoreUnmapped); } + public GeoDistanceQueryBuilder strategy(SpatialStrategy strategy) { + if (strategy != null && strategy == SpatialStrategy.TERM && relation != ShapeRelation.INTERSECTS) { + throw new IllegalArgumentException("strategy [" + strategy.getStrategyName() + "] only supports relation [" + + ShapeRelation.INTERSECTS.getRelationName() + "] found relation [" + relation.getRelationName() + "]"); + } + this.strategy = strategy; + return this; + } + /** Name of the field this query is operating on. */ public String fieldName() { return this.fieldName; @@ -225,36 +257,73 @@ public boolean ignoreUnmapped() { return ignoreUnmapped; } +// @Override +// protected Query doToQuery(QueryShardContext shardContext) throws IOException { +// MappedFieldType fieldType = shardContext.fieldMapper(fieldName); +// if (fieldType == null) { +// if (ignoreUnmapped) { +// return new MatchNoDocsQuery(); +// } else { +// throw new QueryShardException(shardContext, "failed to find geo_point field [" + fieldName + "]"); +// } +// } + +// if (!(fieldType instanceof GeoPointFieldType)) { +// throw new QueryShardException(shardContext, "field [" + fieldName + "] is not a geo_point field"); +// } + +// if (!validContentTypes().contains(fieldType.typeName())) { +// throw new QueryShardException(shardContext, +// "Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "]. [" +// + NAME + "] query supports the following types [" +// + String.join(",", validContentTypes()) + "]"); +// } +// +// QueryValidationException exception = checkLatLon(); +// if (exception != null) { +// throw new QueryShardException(shardContext, "couldn't validate latitude/ longitude values", exception); +// } +// +// if (GeoValidationMethod.isCoerce(validationMethod)) { +// GeoUtils.normalizePoint(center, true, true); +// } + +// Query query = LatLonPoint.newDistanceQuery(fieldType.name(), center.lat(), center.lon(), this.distance); +// if (fieldType.hasDocValues()) { +// Query dvQuery = LatLonDocValuesField.newSlowDistanceQuery(fieldType.name(), center.lat(), center.lon(), this.distance); +// query = new IndexOrDocValuesQuery(query, dvQuery); +// } +// return query; +// } @Override - protected Query doToQuery(QueryShardContext shardContext) throws IOException { - MappedFieldType fieldType = shardContext.fieldMapper(fieldName); - if (fieldType == null) { - if (ignoreUnmapped) { - return new MatchNoDocsQuery(); - } else { - throw new QueryShardException(shardContext, "failed to find geo_point field [" + fieldName + "]"); - } + public Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType) { + if (!validContentTypes().contains(fieldType.typeName())) { + throw new QueryShardException(context, + "Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "]. [" + + NAME + "] query supports the following types [" + + String.join(",", validContentTypes()) + "]"); } - if (!(fieldType instanceof GeoPointFieldType)) { - throw new QueryShardException(shardContext, "field [" + fieldName + "] is not a geo_point field"); - } + final AbstractSearchableGeometryFieldType ft = + (AbstractSearchableGeometryFieldType) fieldType; + return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, fieldName, strategy, relation, context)); + } - QueryValidationException exception = checkLatLon(); - if (exception != null) { - throw new QueryShardException(shardContext, "couldn't validate latitude/ longitude values", exception); + @Override + protected void doShapeQueryXContent(XContentBuilder builder, Params params) throws IOException { + if (strategy != null) { + builder.field(STRATEGY_FIELD.getPreferredName(), strategy.getStrategyName()); } + } - if (GeoValidationMethod.isCoerce(validationMethod)) { - GeoUtils.normalizePoint(center, true, true); - } + @Override + protected GeoDistanceQueryBuilder newShapeQueryBuilder(String fieldName, Geometry shape) { + return new GeoDistanceQueryBuilder(fieldName, shape); + } - Query query = LatLonPoint.newDistanceQuery(fieldType.name(), center.lat(), center.lon(), this.distance); - if (fieldType.hasDocValues()) { - Query dvQuery = LatLonDocValuesField.newSlowDistanceQuery(fieldType.name(), center.lat(), center.lon(), this.distance); - query = new IndexOrDocValuesQuery(query, dvQuery); - } - return query; + @Override + protected GeoDistanceQueryBuilder newShapeQueryBuilder(String fieldName, Supplier shapeSupplier, String indexedShapeId) { + return new GeoDistanceQueryBuilder(fieldName, shapeSupplier, indexedShapeId); } @Override @@ -269,105 +338,168 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep builder.endObject(); } - public static GeoDistanceQueryBuilder fromXContent(XContentParser parser) throws IOException { - XContentParser.Token token; - - float boost = AbstractQueryBuilder.DEFAULT_BOOST; - String queryName = null; - String currentFieldName = null; - GeoPoint point = new GeoPoint(Double.NaN, Double.NaN); - String fieldName = null; - Object vDistance = null; - DistanceUnit unit = GeoDistanceQueryBuilder.DEFAULT_DISTANCE_UNIT; - GeoDistance geoDistance = GeoDistanceQueryBuilder.DEFAULT_GEO_DISTANCE; - GeoValidationMethod validationMethod = null; - boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED; - - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token == XContentParser.Token.START_ARRAY) { - fieldName = currentFieldName; - GeoUtils.parseGeoPoint(parser, point); - } else if (token == XContentParser.Token.START_OBJECT) { - throwParsingExceptionOnMultipleFields(NAME, parser.getTokenLocation(), fieldName, currentFieldName); - // the json in the format of -> field : { lat : 30, lon : 12 } - String currentName = parser.currentName(); - fieldName = currentFieldName; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentName = parser.currentName(); - } else if (token.isValue()) { - if (currentName.equals("lat")) { - point.resetLat(parser.doubleValue()); - } else if (currentName.equals("lon")) { - point.resetLon(parser.doubleValue()); - } else if (currentName.equals("geohash")) { - point.resetFromGeoHash(parser.text()); - } else { - throw new ParsingException(parser.getTokenLocation(), - "[geo_distance] query does not support [" + currentFieldName + "]"); - } - } - } - } else if (token.isValue()) { - if (DISTANCE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - if (token == XContentParser.Token.VALUE_STRING) { - vDistance = parser.text(); // a String - } else { - vDistance = parser.numberValue(); // a Number - } - } else if (UNIT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - unit = DistanceUnit.fromString(parser.text()); - } else if (DISTANCE_TYPE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - geoDistance = GeoDistance.fromString(parser.text()); - } else if (currentFieldName.endsWith(".lat")) { - point.resetLat(parser.doubleValue()); - fieldName = currentFieldName.substring(0, currentFieldName.length() - ".lat".length()); - } else if (currentFieldName.endsWith(".lon")) { - point.resetLon(parser.doubleValue()); - fieldName = currentFieldName.substring(0, currentFieldName.length() - ".lon".length()); - } else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - queryName = parser.text(); - } else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - boost = parser.floatValue(); - } else if (IGNORE_UNMAPPED_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - ignoreUnmapped = parser.booleanValue(); - } else if (VALIDATION_METHOD_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - validationMethod = GeoValidationMethod.fromString(parser.text()); +// public static GeoDistanceQueryBuilder fromXContent(XContentParser parser) throws IOException { +// XContentParser.Token token; +// +// float boost = AbstractQueryBuilder.DEFAULT_BOOST; +// String queryName = null; +// String currentFieldName = null; +// GeoPoint point = new GeoPoint(Double.NaN, Double.NaN); +// String fieldName = null; +// Object vDistance = null; +// DistanceUnit unit = GeoDistanceQueryBuilder.DEFAULT_DISTANCE_UNIT; +// GeoDistance geoDistance = GeoDistanceQueryBuilder.DEFAULT_GEO_DISTANCE; +// GeoValidationMethod validationMethod = null; +// boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED; +// +// while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { +// if (token == XContentParser.Token.FIELD_NAME) { +// currentFieldName = parser.currentName(); +// } else if (token == XContentParser.Token.START_ARRAY) { +// fieldName = currentFieldName; +// GeoUtils.parseGeoPoint(parser, point); +// } else if (token == XContentParser.Token.START_OBJECT) { +// throwParsingExceptionOnMultipleFields(NAME, parser.getTokenLocation(), fieldName, currentFieldName); +// // the json in the format of -> field : { lat : 30, lon : 12 } +// String currentName = parser.currentName(); +// fieldName = currentFieldName; +// while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { +// if (token == XContentParser.Token.FIELD_NAME) { +// currentName = parser.currentName(); +// } else if (token.isValue()) { +// if (currentName.equals("lat")) { +// point.resetLat(parser.doubleValue()); +// } else if (currentName.equals("lon")) { +// point.resetLon(parser.doubleValue()); +// } else if (currentName.equals("geohash")) { +// point.resetFromGeoHash(parser.text()); +// } else { +// throw new ParsingException(parser.getTokenLocation(), +// "[geo_distance] query does not support [" + currentFieldName + "]"); +// } +// } +// } +// } else if (token.isValue()) { +// if (DISTANCE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// if (token == XContentParser.Token.VALUE_STRING) { +// vDistance = parser.text(); // a String +// } else { +// vDistance = parser.numberValue(); // a Number +// } +// } else if (UNIT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// unit = DistanceUnit.fromString(parser.text()); +// } else if (DISTANCE_TYPE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// geoDistance = GeoDistance.fromString(parser.text()); +// } else if (currentFieldName.endsWith(".lat")) { +// point.resetLat(parser.doubleValue()); +// fieldName = currentFieldName.substring(0, currentFieldName.length() - ".lat".length()); +// } else if (currentFieldName.endsWith(".lon")) { +// point.resetLon(parser.doubleValue()); +// fieldName = currentFieldName.substring(0, currentFieldName.length() - ".lon".length()); +// } else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// queryName = parser.text(); +// } else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// boost = parser.floatValue(); +// } else if (IGNORE_UNMAPPED_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// ignoreUnmapped = parser.booleanValue(); +// } else if (VALIDATION_METHOD_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { +// validationMethod = GeoValidationMethod.fromString(parser.text()); +// } else { +// if (fieldName == null) { +// point.resetFromString(parser.text()); +// fieldName = currentFieldName; +// } else { +// throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. unexpected field [{}]", +// NAME, currentFieldName); +// } +// } +// } +// } +// +// if (vDistance == null) { +// throw new ParsingException(parser.getTokenLocation(), "geo_distance requires 'distance' to be specified"); +// } +// +// GeoDistanceQueryBuilder qb = new GeoDistanceQueryBuilder(fieldName); +// if (vDistance instanceof Number) { +// qb.distance(((Number) vDistance).doubleValue(), unit); +// } else { +// qb.distance((String) vDistance, unit); +// } +// qb.point(point); +// if (validationMethod != null) { +// qb.setValidationMethod(validationMethod); +// } +// qb.geoDistance(geoDistance); +// qb.boost(boost); +// qb.queryName(queryName); +// qb.ignoreUnmapped(ignoreUnmapped); +// return qb; +// } + private static class ParsedGeoShapeQueryParams extends ParsedGeometryQueryParams { + SpatialStrategy strategy; + + @Override + protected boolean parseXContentField(XContentParser parser) throws IOException { + SpatialStrategy strategy; + if (SHAPE_FIELD.match(parser.currentName(), parser.getDeprecationHandler())) { + this.shape = ShapeParser.parse(parser); + return true; + } else if (STRATEGY_FIELD.match(parser.currentName(), parser.getDeprecationHandler())) { + String strategyName = parser.text(); + strategy = SpatialStrategy.fromString(strategyName); + if (strategy == null) { + throw new ParsingException(parser.getTokenLocation(), "Unknown strategy [" + strategyName + " ]"); } else { - if (fieldName == null) { - point.resetFromString(parser.text()); - fieldName = currentFieldName; - } else { - throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. unexpected field [{}]", - NAME, currentFieldName); - } + this.strategy = strategy; } + return true; } + return false; } + } + public static GeoDistanceQueryBuilder fromXContent(XContentParser parser) throws IOException { + ParsedGeoShapeQueryParams pgsqp = + (ParsedGeoShapeQueryParams) AbstractGeometryQueryBuilder.parsedParamsFromXContent(parser, new ParsedGeoShapeQueryParams()); - if (vDistance == null) { - throw new ParsingException(parser.getTokenLocation(), "geo_distance requires 'distance' to be specified"); - } + GeoDistanceQueryBuilder builder; - GeoDistanceQueryBuilder qb = new GeoDistanceQueryBuilder(fieldName); - if (vDistance instanceof Number) { - qb.distance(((Number) vDistance).doubleValue(), unit); + if (pgsqp.shape != null) { + builder = new GeoDistanceQueryBuilder(pgsqp.fieldName, pgsqp.shape); } else { - qb.distance((String) vDistance, unit); + builder = new GeoDistanceQueryBuilder(pgsqp.fieldName, pgsqp.id); } - qb.point(point); - if (validationMethod != null) { - qb.setValidationMethod(validationMethod); + + if (pgsqp.index != null) { + builder.indexedShapeIndex(pgsqp.index); } - qb.geoDistance(geoDistance); - qb.boost(boost); - qb.queryName(queryName); - qb.ignoreUnmapped(ignoreUnmapped); - return qb; + + if (pgsqp.shapePath != null) { + builder.indexedShapePath(pgsqp.shapePath); + } + + if (pgsqp.shapeRouting != null) { + builder.indexedShapeRouting(pgsqp.shapeRouting); + } + + if (pgsqp.relation != null) { + builder.relation(pgsqp.relation); + } + + if (pgsqp.strategy != null) { + builder.strategy(pgsqp.strategy); + } + + if (pgsqp.queryName != null) { + builder.queryName(pgsqp.queryName); + } + + builder.boost(pgsqp.boost); + builder.ignoreUnmapped(pgsqp.ignoreUnmapped); + return builder; } + @Override protected int doHashCode() { return Objects.hash(center, geoDistance, distance, validationMethod, ignoreUnmapped);