New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sorting: Allow _geo_distance to handle many to many geo point distance #7097
Changes from 1 commit
172d832
38baefb
79c6c3a
c221f92
ded2a80
1287ed8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,12 +19,16 @@ | |
|
||
package org.elasticsearch.search.sort; | ||
|
||
import com.google.common.collect.Lists; | ||
import org.elasticsearch.common.geo.GeoDistance; | ||
import org.elasticsearch.common.geo.GeoPoint; | ||
import org.elasticsearch.common.unit.DistanceUnit; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
import org.elasticsearch.index.query.FilterBuilder; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
/** | ||
|
@@ -33,10 +37,8 @@ | |
public class GeoDistanceSortBuilder extends SortBuilder { | ||
|
||
final String fieldName; | ||
|
||
private double lat; | ||
private double lon; | ||
private String geohash; | ||
private final List<GeoPoint> points = new ArrayList<>(); | ||
private final List<String> geohashes = new ArrayList<>(); | ||
|
||
private GeoDistance geoDistance; | ||
private DistanceUnit unit; | ||
|
@@ -61,16 +63,25 @@ public GeoDistanceSortBuilder(String fieldName) { | |
* @param lon longitude. | ||
*/ | ||
public GeoDistanceSortBuilder point(double lat, double lon) { | ||
this.lat = lat; | ||
this.lon = lon; | ||
points.add(new GeoPoint(lat, lon)); | ||
return this; | ||
} | ||
|
||
/** | ||
* The point to create the range distance facets from. | ||
* | ||
* @param points reference points. | ||
*/ | ||
public GeoDistanceSortBuilder points(GeoPoint... points) { | ||
this.points.addAll(Lists.newArrayList(points)); | ||
return this; | ||
} | ||
|
||
/** | ||
* The geohash of the geo point to create the range distance facets from. | ||
*/ | ||
public GeoDistanceSortBuilder geohash(String geohash) { | ||
this.geohash = geohash; | ||
public GeoDistanceSortBuilder geohash(String... geohash) { | ||
this.geohashes.addAll(Lists.newArrayList(geohash)); | ||
return this; | ||
} | ||
|
||
|
@@ -138,10 +149,18 @@ public GeoDistanceSortBuilder setNestedPath(String nestedPath) { | |
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { | ||
builder.startObject("_geo_distance"); | ||
|
||
if (geohash != null) { | ||
builder.field(fieldName, geohash); | ||
if (geohashes.size() != 0) { | ||
if (geohashes.size() == 1) { | ||
builder.field(fieldName, geohashes.get(0)); | ||
} else { | ||
builder.field(fieldName, geohashes); | ||
} | ||
} else { | ||
builder.startArray(fieldName).value(lon).value(lat).endArray(); | ||
if (points.size() == 1) { | ||
builder.field(fieldName, points.get(0)); | ||
} else { | ||
builder.field(fieldName, points); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that it supports several points, should it also work when both geo hashes and geo points are provided? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. makes sense, changed that in "make sort work with arbitrary mixed geo_points formats" |
||
|
||
if (unit != null) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,12 +26,14 @@ | |
import org.apache.lucene.search.SortField; | ||
import org.apache.lucene.util.FixedBitSet; | ||
import org.elasticsearch.ElasticsearchIllegalArgumentException; | ||
import org.elasticsearch.ElasticsearchParseException; | ||
import org.elasticsearch.common.bytes.BytesArray; | ||
import org.elasticsearch.common.geo.GeoDistance; | ||
import org.elasticsearch.common.geo.GeoDistance.FixedSourceDistance; | ||
import org.elasticsearch.common.geo.GeoPoint; | ||
import org.elasticsearch.common.geo.GeoUtils; | ||
import org.elasticsearch.common.unit.DistanceUnit; | ||
import org.elasticsearch.common.xcontent.XContentParser; | ||
import org.elasticsearch.common.xcontent.*; | ||
import org.elasticsearch.index.fielddata.*; | ||
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested; | ||
import org.elasticsearch.index.mapper.FieldMapper; | ||
|
@@ -43,6 +45,10 @@ | |
import org.elasticsearch.search.internal.SearchContext; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; | ||
|
||
/** | ||
* | ||
|
@@ -57,7 +63,7 @@ public String[] names() { | |
@Override | ||
public SortField parse(XContentParser parser, SearchContext context) throws Exception { | ||
String fieldName = null; | ||
GeoPoint point = new GeoPoint(); | ||
List<GeoPoint> geoPoints = new ArrayList<>(); | ||
DistanceUnit unit = DistanceUnit.DEFAULT; | ||
GeoDistance geoDistance = GeoDistance.DEFAULT; | ||
boolean reverse = false; | ||
|
@@ -74,7 +80,8 @@ public SortField parse(XContentParser parser, SearchContext context) throws Exce | |
if (token == XContentParser.Token.FIELD_NAME) { | ||
currentName = parser.currentName(); | ||
} else if (token == XContentParser.Token.START_ARRAY) { | ||
GeoUtils.parseGeoPoint(parser, point); | ||
parseGeoPoints(parser, geoPoints); | ||
|
||
fieldName = currentName; | ||
} else if (token == XContentParser.Token.START_OBJECT) { | ||
// the json in the format of -> field : { lat : 30, lon : 12 } | ||
|
@@ -83,7 +90,9 @@ public SortField parse(XContentParser parser, SearchContext context) throws Exce | |
nestedFilter = parsedFilter == null ? null : parsedFilter.filter(); | ||
} else { | ||
fieldName = currentName; | ||
GeoPoint point = new GeoPoint(); | ||
GeoUtils.parseGeoPoint(parser, point); | ||
geoPoints.add(point); | ||
} | ||
} else if (token.isValue()) { | ||
if ("reverse".equals(currentName)) { | ||
|
@@ -102,14 +111,18 @@ public SortField parse(XContentParser parser, SearchContext context) throws Exce | |
} else if ("nested_path".equals(currentName) || "nestedPath".equals(currentName)) { | ||
nestedPath = parser.text(); | ||
} else { | ||
GeoPoint point = new GeoPoint(); | ||
point.resetFromString(parser.text()); | ||
geoPoints.add(point); | ||
fieldName = currentName; | ||
} | ||
} | ||
} | ||
|
||
if (normalizeLat || normalizeLon) { | ||
GeoUtils.normalizePoint(point, normalizeLat, normalizeLon); | ||
for (GeoPoint point : geoPoints) { | ||
GeoUtils.normalizePoint(point, normalizeLat, normalizeLon); | ||
} | ||
} | ||
|
||
if (sortMode == null) { | ||
|
@@ -126,7 +139,10 @@ public SortField parse(XContentParser parser, SearchContext context) throws Exce | |
} | ||
final MultiValueMode finalSortMode = sortMode; // final reference for use in the anonymous class | ||
final IndexGeoPointFieldData geoIndexFieldData = context.fieldData().getForField(mapper); | ||
final FixedSourceDistance distance = geoDistance.fixedSourceDistance(point.lat(), point.lon(), unit); | ||
final FixedSourceDistance[] distances = new FixedSourceDistance[geoPoints.size()]; | ||
for (int i = 0; i< geoPoints.size(); i++) { | ||
distances[i] = geoDistance.fixedSourceDistance(geoPoints.get(i).lat(), geoPoints.get(i).lon(), unit); | ||
} | ||
ObjectMapper objectMapper; | ||
if (nestedPath != null) { | ||
ObjectMappers objectMappers = context.mapperService().objectMapper(nestedPath); | ||
|
@@ -167,7 +183,7 @@ public FieldComparator<?> newComparator(String fieldname, int numHits, int sortP | |
@Override | ||
protected Doubles getDoubleValues(AtomicReaderContext context, String field) throws IOException { | ||
final MultiGeoPointValues geoPointValues = geoIndexFieldData.load(context).getGeoPointValues(); | ||
final SortedNumericDoubleValues distanceValues = GeoDistance.distanceValues(distance, geoPointValues); | ||
final SortedNumericDoubleValues distanceValues = GeoDistance.distanceValues(geoPointValues, distances); | ||
final NumericDoubleValues selectedValues; | ||
if (nested == null) { | ||
selectedValues = finalSortMode.select(distanceValues, Double.MAX_VALUE); | ||
|
@@ -190,4 +206,31 @@ public double get(int docID) { | |
|
||
return new SortField(fieldName, geoDistanceComparatorSource, reverse); | ||
} | ||
|
||
private void parseGeoPoints(XContentParser parser, List<GeoPoint> geoPoints) throws IOException { | ||
|
||
XContentBuilder parserCopy = jsonBuilder(); | ||
parserCopy.copyCurrentStructure(parser); | ||
String dummyField = "{\"dummy_field\":" + parserCopy.string() + "}"; | ||
XContentParser copiedParser = XContentHelper.createParser(new BytesArray(dummyField)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm, is there a cleaner possibility? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 on cleaning this up There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this now in commit "clean up parsing: don't copy structure and parse twice". The problem was that |
||
try { | ||
copiedParser.nextToken(); | ||
copiedParser.nextToken(); | ||
copiedParser.nextToken(); | ||
while (!copiedParser.nextToken().equals(XContentParser.Token.END_ARRAY)) { | ||
GeoPoint point = new GeoPoint(); | ||
GeoUtils.parseGeoPoint(copiedParser, point); | ||
geoPoints.add(point); | ||
} | ||
} catch (ElasticsearchParseException e) { | ||
// it might be that the input was [number, number] in which case we must try to parse again | ||
copiedParser = XContentHelper.createParser(new BytesArray(dummyField)); | ||
GeoPoint point = new GeoPoint(); | ||
copiedParser.nextToken(); | ||
copiedParser.nextToken(); | ||
copiedParser.nextToken(); | ||
GeoUtils.parseGeoPoint(copiedParser, point); | ||
geoPoints.add(point); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,8 +30,11 @@ | |
import org.elasticsearch.action.search.SearchResponse; | ||
import org.elasticsearch.action.search.ShardSearchFailure; | ||
import org.elasticsearch.cluster.metadata.IndexMetaData; | ||
import org.elasticsearch.common.geo.GeoDistance; | ||
import org.elasticsearch.common.geo.GeoPoint; | ||
import org.elasticsearch.common.text.StringAndBytesText; | ||
import org.elasticsearch.common.text.Text; | ||
import org.elasticsearch.common.unit.DistanceUnit; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
import org.elasticsearch.common.xcontent.XContentFactory; | ||
import org.elasticsearch.index.mapper.Uid; | ||
|
@@ -1675,5 +1678,70 @@ public void testSortDuelBetweenSingleShardAndMultiShardIndex() throws Exception | |
} | ||
} | ||
|
||
public void testManyToManyGeoPoints() throws ExecutionException, InterruptedException { | ||
/** | ||
* | q | d1 | d2 | ||
* | | | | ||
* | | | | ||
* | | | | ||
* |2 o| x | x | ||
* | | | | ||
* |1 o| x | x | ||
* |___________________________ | ||
* 1 2 3 4 5 6 7 | ||
*/ | ||
assertAcked(prepareCreate("index") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :-) |
||
.addMapping("type", "{\"type\": {\"properties\": {\"location\": {\"type\": \"geo_point\"}}}}")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or more simply: .addMapping("type", "location", "type=geo_point") |
||
String d1 = randomBoolean() ? | ||
"{\"location\": [{\"lat\": \"3\", \"lon\": \"2\"}, {\"lat\": \"4\", \"lon\": \"1\"}]}" : | ||
"{\"location\": [{\"lat\": \"4\", \"lon\": \"1\"}, {\"lat\": \"3\", \"lon\": \"2\"}]}"; | ||
String d2 = randomBoolean() ? | ||
"{\"location\": [{\"lat\": \"5\", \"lon\": \"1\"}, {\"lat\": \"6\", \"lon\": \"2\"}]}" : | ||
"{\"location\": [{\"lat\": \"6\", \"lon\": \"2\"}, {\"lat\": \"5\", \"lon\": \"1\"}]}"; | ||
indexRandom(true, | ||
client().prepareIndex("index", "type", "d1").setSource(d1), | ||
client().prepareIndex("index", "type", "d2").setSource(d2)); | ||
ensureYellow(); | ||
GeoPoint[] q = new GeoPoint[2]; | ||
if (randomBoolean()) { | ||
q[0] = new GeoPoint(2, 1); | ||
q[1] = new GeoPoint(2, 2); | ||
} else { | ||
q[1] = new GeoPoint(2, 2); | ||
q[0] = new GeoPoint(2, 1); | ||
} | ||
|
||
SearchResponse searchResponse = client().prepareSearch() | ||
.setQuery(matchAllQuery()) | ||
.addSort(new GeoDistanceSortBuilder("location").points(q).sortMode("min").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) | ||
.execute().actionGet(); | ||
assertOrderedSearchHits(searchResponse, "d1", "d2"); | ||
assertThat((Double) searchResponse.getHits().getAt(0).getSortValues()[0], equalTo(GeoDistance.PLANE.calculate(2, 2, 3, 2, DistanceUnit.KILOMETERS))); | ||
assertThat((Double) searchResponse.getHits().getAt(1).getSortValues()[0], equalTo(GeoDistance.PLANE.calculate(2, 1, 5, 1, DistanceUnit.KILOMETERS))); | ||
|
||
searchResponse = client().prepareSearch() | ||
.setQuery(matchAllQuery()) | ||
.addSort(new GeoDistanceSortBuilder("location").points(q).sortMode("min").order(SortOrder.DESC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) | ||
.execute().actionGet(); | ||
assertOrderedSearchHits(searchResponse, "d2", "d1"); | ||
assertThat((Double) searchResponse.getHits().getAt(0).getSortValues()[0], equalTo(GeoDistance.PLANE.calculate(2, 1, 5, 1, DistanceUnit.KILOMETERS))); | ||
assertThat((Double) searchResponse.getHits().getAt(1).getSortValues()[0], equalTo(GeoDistance.PLANE.calculate(2, 2, 3, 2, DistanceUnit.KILOMETERS))); | ||
|
||
searchResponse = client().prepareSearch() | ||
.setQuery(matchAllQuery()) | ||
.addSort(new GeoDistanceSortBuilder("location").points(q).sortMode("max").order(SortOrder.ASC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) | ||
.execute().actionGet(); | ||
assertOrderedSearchHits(searchResponse, "d1", "d2"); | ||
assertThat((Double) searchResponse.getHits().getAt(0).getSortValues()[0], equalTo(GeoDistance.PLANE.calculate(2, 2, 4, 1, DistanceUnit.KILOMETERS))); | ||
assertThat((Double) searchResponse.getHits().getAt(1).getSortValues()[0], equalTo(GeoDistance.PLANE.calculate(2, 1, 6, 2, DistanceUnit.KILOMETERS))); | ||
|
||
searchResponse = client().prepareSearch() | ||
.setQuery(matchAllQuery()) | ||
.addSort(new GeoDistanceSortBuilder("location").points(q).sortMode("max").order(SortOrder.DESC).geoDistance(GeoDistance.PLANE).unit(DistanceUnit.KILOMETERS)) | ||
.execute().actionGet(); | ||
assertOrderedSearchHits(searchResponse, "d2", "d1"); | ||
assertThat((Double) searchResponse.getHits().getAt(0).getSortValues()[0], equalTo(GeoDistance.PLANE.calculate(2, 1, 6, 2, DistanceUnit.KILOMETERS))); | ||
assertThat((Double) searchResponse.getHits().getAt(1).getSortValues()[0], equalTo(GeoDistance.PLANE.calculate(2, 2, 4, 1, DistanceUnit.KILOMETERS))); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor thing, but
this.points.addAll(Arrays.asList(points));
would help copying the data only once instead of twice.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, changed also in
geohash(String... geohashes)