org.elasticsearch.index.query.GeoBoundingBoxQueryParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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.index.query;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
import org.apache.lucene.spatial.geopoint.search.GeoPointInBBoxQuery;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.InMemoryGeoBoundingBoxQuery;
import org.elasticsearch.index.search.geo.IndexedGeoBoundingBoxQuery;
import java.io.IOException;
/**
*
*/
public class GeoBoundingBoxQueryParser implements QueryParser {
public static final String NAME = "geo_bbox";
public static final String TOP = "top";
public static final String LEFT = "left";
public static final String RIGHT = "right";
public static final String BOTTOM = "bottom";
public static final String TOP_LEFT = TOP + "_" + LEFT;
public static final String TOP_RIGHT = TOP + "_" + RIGHT;
public static final String BOTTOM_LEFT = BOTTOM + "_" + LEFT;
public static final String BOTTOM_RIGHT = BOTTOM + "_" + RIGHT;
public static final String TOPLEFT = "topLeft";
public static final String TOPRIGHT = "topRight";
public static final String BOTTOMLEFT = "bottomLeft";
public static final String BOTTOMRIGHT = "bottomRight";
public static final String FIELD = "field";
@Inject
public GeoBoundingBoxQueryParser() {
}
@Override
public String[] names() {
return new String[]{NAME, "geoBbox", "geo_bounding_box", "geoBoundingBox"};
}
@Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
String fieldName = null;
double top = Double.NaN;
double bottom = Double.NaN;
double left = Double.NaN;
double right = Double.NaN;
String queryName = null;
String currentFieldName = null;
XContentParser.Token token;
final boolean indexCreatedBeforeV2_0 = parseContext.indexVersionCreated().before(Version.V_2_0_0);
boolean coerce = false;
boolean ignoreMalformed = false;
GeoPoint sparse = new GeoPoint();
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) {
fieldName = currentFieldName;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
token = parser.nextToken();
if (parseContext.isDeprecatedSetting(currentFieldName)) {
// skip
} else if (FIELD.equals(currentFieldName)) {
fieldName = parser.text();
} else if (TOP.equals(currentFieldName)) {
top = parser.doubleValue();
} else if (BOTTOM.equals(currentFieldName)) {
bottom = parser.doubleValue();
} else if (LEFT.equals(currentFieldName)) {
left = parser.doubleValue();
} else if (RIGHT.equals(currentFieldName)) {
right = parser.doubleValue();
} else {
if (TOP_LEFT.equals(currentFieldName) || TOPLEFT.equals(currentFieldName)) {
GeoUtils.parseGeoPoint(parser, sparse);
top = sparse.getLat();
left = sparse.getLon();
} else if (BOTTOM_RIGHT.equals(currentFieldName) || BOTTOMRIGHT.equals(currentFieldName)) {
GeoUtils.parseGeoPoint(parser, sparse);
bottom = sparse.getLat();
right = sparse.getLon();
} else if (TOP_RIGHT.equals(currentFieldName) || TOPRIGHT.equals(currentFieldName)) {
GeoUtils.parseGeoPoint(parser, sparse);
top = sparse.getLat();
right = sparse.getLon();
} else if (BOTTOM_LEFT.equals(currentFieldName) || BOTTOMLEFT.equals(currentFieldName)) {
GeoUtils.parseGeoPoint(parser, sparse);
bottom = sparse.getLat();
left = sparse.getLon();
} else {
throw new ElasticsearchParseException("failed to parse [{}] query. unexpected field [{}]", NAME, currentFieldName);
}
}
} else {
throw new ElasticsearchParseException("failed to parse [{}] query. field name expected but [{}] found", NAME, token);
}
}
} else if (token.isValue()) {
if ("_name".equals(currentFieldName)) {
queryName = parser.text();
} else if ("coerce".equals(currentFieldName) || (indexCreatedBeforeV2_0 && "normalize".equals(currentFieldName))) {
coerce = parser.booleanValue();
if (coerce == true) {
ignoreMalformed = true;
}
} else if ("type".equals(currentFieldName)) {
type = parser.text();
} else if ("ignore_malformed".equals(currentFieldName) && coerce == false) {
ignoreMalformed = parser.booleanValue();
} else {
throw new QueryParsingException(parseContext, "failed to parse [{}] query. unexpected field [{}]", NAME, currentFieldName);
}
}
}
final GeoPoint topLeft = sparse.reset(top, left); //just keep the object
final GeoPoint bottomRight = new GeoPoint(bottom, right);
// validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes
if (!indexCreatedBeforeV2_0 && !ignoreMalformed) {
if (topLeft.lat() > 90.0 || topLeft.lat() < -90.0) {
throw new QueryParsingException(parseContext, "illegal latitude value [{}] for [{}]", topLeft.lat(), NAME);
}
if (topLeft.lon() > 180.0 || topLeft.lon() < -180) {
throw new QueryParsingException(parseContext, "illegal longitude value [{}] for [{}]", topLeft.lon(), NAME);
}
if (bottomRight.lat() > 90.0 || bottomRight.lat() < -90.0) {
throw new QueryParsingException(parseContext, "illegal latitude value [{}] for [{}]", bottomRight.lat(), NAME);
}
if (bottomRight.lon() > 180.0 || bottomRight.lon() < -180) {
throw new QueryParsingException(parseContext, "illegal longitude value [{}] for [{}]", bottomRight.lon(), NAME);
}
}
final Version indexVersionCreated = parseContext.indexVersionCreated();
if (indexVersionCreated.onOrAfter(Version.V_2_2_0) || coerce) {
// 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 longditude range
boolean completeLonRange = ((right - left) % 360 == 0 && right > left);
GeoUtils.normalizePoint(topLeft, true, !completeLonRange);
GeoUtils.normalizePoint(bottomRight, true, !completeLonRange);
if (completeLonRange) {
topLeft.resetLon(-180);
bottomRight.resetLon(180);
}
}
// validate top.lat > bottom.lat
if (topLeft.lat() < bottomRight.lat()) {
throw new QueryParsingException(parseContext, "top is below bottom corner: [{}] vs [{}]", topLeft.lat(), bottomRight.lat());
} else if (topLeft.lat() == bottomRight.lat()) {
throw new QueryParsingException(parseContext, "top latitude cannot equal bottom latitude: [{}] vs [{}]", topLeft.lat(), bottomRight.lat());
}
MappedFieldType fieldType = parseContext.fieldMapper(fieldName);
if (fieldType == null) {
throw new QueryParsingException(parseContext, "failed to parse [{}] query. could not find [{}] field [{}]", NAME, GeoPointFieldMapper.CONTENT_TYPE, fieldName);
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new QueryParsingException(parseContext, "failed to parse [{}] query. field [{}] is expected to be of type [{}], but is of [{}] type instead", NAME, fieldName, GeoPointFieldMapper.CONTENT_TYPE, fieldType.typeName());
}
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType);
Query query;
if (indexVersionCreated.onOrAfter(Version.V_2_2_0)) {
// if index created V_2_2 use (soon to be legacy) numeric encoding postings format
// if index created V_2_3 > use prefix encoded postings format
final GeoPointField.TermEncoding encoding = (indexVersionCreated.before(Version.V_2_3_0)) ?
GeoPointField.TermEncoding.NUMERIC : GeoPointField.TermEncoding.PREFIX;
query = new GeoPointInBBoxQuery(fieldType.names().fullName(), encoding, topLeft.lon(), bottomRight.lat(),
bottomRight.lon(), topLeft.lat());
} else if ("indexed".equals(type)) {
query = IndexedGeoBoundingBoxQuery.create(topLeft, bottomRight, geoFieldType);
} else if ("memory".equals(type)) {
IndexGeoPointFieldData indexFieldData = parseContext.getForField(fieldType);
query = new InMemoryGeoBoundingBoxQuery(topLeft, bottomRight, indexFieldData);
} else {
throw new QueryParsingException(parseContext, "failed to parse [{}] query. geo bounding box type [{}] is not supported. either [indexed] or [memory] are allowed", NAME, type);
}
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query;
}
}