Skip to content

Commit 5af8448

Browse files
brweareek
authored andcommitted
_geo_distance sort: allow many to many geo point distance
Add computation of disyance to many geo points. Example request: ``` { "sort": [ { "_geo_distance": { "location": [ { "lat":1.2, "lon":3 }, { "lat":1.2, "lon":3 } ], "order": "desc", "unit": "km", "sort_mode": "max" } } ] } ``` closes #3926
1 parent 2294699 commit 5af8448

File tree

9 files changed

+477
-30
lines changed

9 files changed

+477
-30
lines changed

docs/reference/search/request/sort.asciidoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,22 @@ conform with http://geojson.org/[GeoJSON].
263263
}
264264
--------------------------------------------------
265265

266+
267+
==== Multiple reference points
268+
269+
Multiple geo points can be passed as an array containing any `geo_point` format, for example
270+
271+
```
272+
"pin.location" : [[-70, 40], [-71, 42]]
273+
"pin.location" : [{"lat": -70, "lon": 40}, {"lat": -71, "lon": 42}]
274+
275+
```
276+
and so forth.
277+
278+
The final distance for a document will then be `min`/`max` distance of all points contained in the document to all points given in the sort request.
279+
280+
281+
266282
==== Script Based Sorting
267283

268284
Allow to sort based on custom scripts, here is an example:

src/main/java/org/elasticsearch/common/geo/GeoDistance.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.common.unit.DistanceUnit;
2626
import org.elasticsearch.index.fielddata.*;
2727

28+
import java.util.List;
2829
import java.util.Locale;
2930

3031
/**
@@ -371,12 +372,13 @@ public double calculate(double targetLatitude, double targetLongitude) {
371372
}
372373
}
373374

375+
374376
/**
375-
* Return a {@link SortedNumericDoubleValues} instance that returns the distance to a given geo-point for each document.
377+
* Return a {@link SortedNumericDoubleValues} instance that returns the distances to a list of geo-points for each document.
376378
*/
377-
public static SortedNumericDoubleValues distanceValues(final FixedSourceDistance distance, final MultiGeoPointValues geoPointValues) {
379+
public static SortedNumericDoubleValues distanceValues(final MultiGeoPointValues geoPointValues, final FixedSourceDistance... distances) {
378380
final GeoPointValues singleValues = FieldData.unwrapSingleton(geoPointValues);
379-
if (singleValues != null) {
381+
if (singleValues != null && distances.length == 1) {
380382
final Bits docsWithField = FieldData.unwrapSingletonBits(geoPointValues);
381383
return FieldData.singleton(new NumericDoubleValues() {
382384

@@ -386,7 +388,7 @@ public double get(int docID) {
386388
return 0d;
387389
}
388390
final GeoPoint point = singleValues.get(docID);
389-
return distance.calculate(point.lat(), point.lon());
391+
return distances[0].calculate(point.lat(), point.lon());
390392
}
391393

392394
}, docsWithField);
@@ -396,15 +398,18 @@ public double get(int docID) {
396398
@Override
397399
public void setDocument(int doc) {
398400
geoPointValues.setDocument(doc);
399-
count = geoPointValues.count();
401+
count = geoPointValues.count() * distances.length;
400402
grow();
401-
for (int i = 0; i < count; ++i) {
402-
final GeoPoint point = geoPointValues.valueAt(i);
403-
values[i] = distance.calculate(point.lat(), point.lon());
403+
int valueCounter = 0;
404+
for (FixedSourceDistance distance : distances) {
405+
for (int i = 0; i < geoPointValues.count(); ++i) {
406+
final GeoPoint point = geoPointValues.valueAt(i);
407+
values[valueCounter] = distance.calculate(point.lat(), point.lon());
408+
valueCounter++;
409+
}
404410
}
405411
sort();
406412
}
407-
408413
};
409414
}
410415
}

src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/GeoDistanceParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public DistanceSource(ValuesSource.GeoPoint source, GeoDistance distanceType, or
209209
public void setNextReader(AtomicReaderContext reader) {
210210
final MultiGeoPointValues geoValues = source.geoPointValues();
211211
final FixedSourceDistance distance = distanceType.fixedSourceDistance(origin.getLat(), origin.getLon(), unit);
212-
distanceValues = GeoDistance.distanceValues(distance, geoValues);
212+
distanceValues = GeoDistance.distanceValues(geoValues, distance);
213213
}
214214

215215
@Override

src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@
1919

2020
package org.elasticsearch.search.sort;
2121

22+
import org.elasticsearch.ElasticsearchParseException;
2223
import org.elasticsearch.common.geo.GeoDistance;
24+
import org.elasticsearch.common.geo.GeoPoint;
2325
import org.elasticsearch.common.unit.DistanceUnit;
2426
import org.elasticsearch.common.xcontent.XContentBuilder;
2527
import org.elasticsearch.index.query.FilterBuilder;
2628

2729
import java.io.IOException;
30+
import java.util.ArrayList;
31+
import java.util.Arrays;
32+
import java.util.List;
2833
import java.util.Locale;
2934

3035
/**
@@ -33,10 +38,8 @@
3338
public class GeoDistanceSortBuilder extends SortBuilder {
3439

3540
final String fieldName;
36-
37-
private double lat;
38-
private double lon;
39-
private String geohash;
41+
private final List<GeoPoint> points = new ArrayList<>();
42+
private final List<String> geohashes = new ArrayList<>();
4043

4144
private GeoDistance geoDistance;
4245
private DistanceUnit unit;
@@ -61,16 +64,25 @@ public GeoDistanceSortBuilder(String fieldName) {
6164
* @param lon longitude.
6265
*/
6366
public GeoDistanceSortBuilder point(double lat, double lon) {
64-
this.lat = lat;
65-
this.lon = lon;
67+
points.add(new GeoPoint(lat, lon));
68+
return this;
69+
}
70+
71+
/**
72+
* The point to create the range distance facets from.
73+
*
74+
* @param points reference points.
75+
*/
76+
public GeoDistanceSortBuilder points(GeoPoint... points) {
77+
this.points.addAll(Arrays.asList(points));
6678
return this;
6779
}
6880

6981
/**
7082
* The geohash of the geo point to create the range distance facets from.
7183
*/
72-
public GeoDistanceSortBuilder geohash(String geohash) {
73-
this.geohash = geohash;
84+
public GeoDistanceSortBuilder geohashes(String... geohashes) {
85+
this.geohashes.addAll(Arrays.asList(geohashes));
7486
return this;
7587
}
7688

@@ -137,13 +149,23 @@ public GeoDistanceSortBuilder setNestedPath(String nestedPath) {
137149
@Override
138150
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
139151
builder.startObject("_geo_distance");
140-
141-
if (geohash != null) {
142-
builder.field(fieldName, geohash);
152+
if (geohashes.size() == 0 && points.size() == 0) {
153+
throw new ElasticsearchParseException("No points provided for _geo_distance sort.");
154+
}
155+
if (geohashes.size() == 1 && points.size() == 0) {
156+
builder.field(fieldName, geohashes.get(0));
157+
} else if (geohashes.size() == 1 && points.size() == 0) {
158+
builder.field(fieldName, points.get(0));
143159
} else {
144-
builder.startArray(fieldName).value(lon).value(lat).endArray();
160+
builder.startArray(fieldName);
161+
for (GeoPoint point : points) {
162+
builder.value(point);
163+
}
164+
for (String geohash : geohashes) {
165+
builder.value(geohash);
166+
}
167+
builder.endArray();
145168
}
146-
147169
if (unit != null) {
148170
builder.field("unit", unit);
149171
}

src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.lucene.search.SortField;
2727
import org.apache.lucene.util.FixedBitSet;
2828
import org.elasticsearch.ElasticsearchIllegalArgumentException;
29+
import org.elasticsearch.ElasticsearchParseException;
2930
import org.elasticsearch.common.geo.GeoDistance;
3031
import org.elasticsearch.common.geo.GeoDistance.FixedSourceDistance;
3132
import org.elasticsearch.common.geo.GeoPoint;
@@ -43,6 +44,8 @@
4344
import org.elasticsearch.search.internal.SearchContext;
4445

4546
import java.io.IOException;
47+
import java.util.ArrayList;
48+
import java.util.List;
4649

4750
/**
4851
*
@@ -57,7 +60,7 @@ public String[] names() {
5760
@Override
5861
public SortField parse(XContentParser parser, SearchContext context) throws Exception {
5962
String fieldName = null;
60-
GeoPoint point = new GeoPoint();
63+
List<GeoPoint> geoPoints = new ArrayList<>();
6164
DistanceUnit unit = DistanceUnit.DEFAULT;
6265
GeoDistance geoDistance = GeoDistance.DEFAULT;
6366
boolean reverse = false;
@@ -74,7 +77,8 @@ public SortField parse(XContentParser parser, SearchContext context) throws Exce
7477
if (token == XContentParser.Token.FIELD_NAME) {
7578
currentName = parser.currentName();
7679
} else if (token == XContentParser.Token.START_ARRAY) {
77-
GeoUtils.parseGeoPoint(parser, point);
80+
parseGeoPoints(parser, geoPoints);
81+
7882
fieldName = currentName;
7983
} else if (token == XContentParser.Token.START_OBJECT) {
8084
// the json in the format of -> field : { lat : 30, lon : 12 }
@@ -83,7 +87,9 @@ public SortField parse(XContentParser parser, SearchContext context) throws Exce
8387
nestedFilter = parsedFilter == null ? null : parsedFilter.filter();
8488
} else {
8589
fieldName = currentName;
90+
GeoPoint point = new GeoPoint();
8691
GeoUtils.parseGeoPoint(parser, point);
92+
geoPoints.add(point);
8793
}
8894
} else if (token.isValue()) {
8995
if ("reverse".equals(currentName)) {
@@ -102,14 +108,18 @@ public SortField parse(XContentParser parser, SearchContext context) throws Exce
102108
} else if ("nested_path".equals(currentName) || "nestedPath".equals(currentName)) {
103109
nestedPath = parser.text();
104110
} else {
111+
GeoPoint point = new GeoPoint();
105112
point.resetFromString(parser.text());
113+
geoPoints.add(point);
106114
fieldName = currentName;
107115
}
108116
}
109117
}
110118

111119
if (normalizeLat || normalizeLon) {
112-
GeoUtils.normalizePoint(point, normalizeLat, normalizeLon);
120+
for (GeoPoint point : geoPoints) {
121+
GeoUtils.normalizePoint(point, normalizeLat, normalizeLon);
122+
}
113123
}
114124

115125
if (sortMode == null) {
@@ -126,7 +136,10 @@ public SortField parse(XContentParser parser, SearchContext context) throws Exce
126136
}
127137
final MultiValueMode finalSortMode = sortMode; // final reference for use in the anonymous class
128138
final IndexGeoPointFieldData geoIndexFieldData = context.fieldData().getForField(mapper);
129-
final FixedSourceDistance distance = geoDistance.fixedSourceDistance(point.lat(), point.lon(), unit);
139+
final FixedSourceDistance[] distances = new FixedSourceDistance[geoPoints.size()];
140+
for (int i = 0; i< geoPoints.size(); i++) {
141+
distances[i] = geoDistance.fixedSourceDistance(geoPoints.get(i).lat(), geoPoints.get(i).lon(), unit);
142+
}
130143
ObjectMapper objectMapper;
131144
if (nestedPath != null) {
132145
ObjectMappers objectMappers = context.mapperService().objectMapper(nestedPath);
@@ -167,7 +180,7 @@ public FieldComparator<?> newComparator(String fieldname, int numHits, int sortP
167180
@Override
168181
protected Doubles getDoubleValues(AtomicReaderContext context, String field) throws IOException {
169182
final MultiGeoPointValues geoPointValues = geoIndexFieldData.load(context).getGeoPointValues();
170-
final SortedNumericDoubleValues distanceValues = GeoDistance.distanceValues(distance, geoPointValues);
183+
final SortedNumericDoubleValues distanceValues = GeoDistance.distanceValues(geoPointValues, distances);
171184
final NumericDoubleValues selectedValues;
172185
if (nested == null) {
173186
selectedValues = finalSortMode.select(distanceValues, Double.MAX_VALUE);
@@ -190,4 +203,27 @@ public double get(int docID) {
190203

191204
return new SortField(fieldName, geoDistanceComparatorSource, reverse);
192205
}
206+
207+
private void parseGeoPoints(XContentParser parser, List<GeoPoint> geoPoints) throws IOException {
208+
while (!parser.nextToken().equals(XContentParser.Token.END_ARRAY)) {
209+
if (parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
210+
// we might get here if the geo point is " number, number] " and the parser already moved over the opening bracket
211+
// in this case we cannot use GeoUtils.parseGeoPoint(..) because this expects an opening bracket
212+
double lon = parser.doubleValue();
213+
parser.nextToken();
214+
if (!parser.currentToken().equals(XContentParser.Token.VALUE_NUMBER)) {
215+
throw new ElasticsearchParseException("geo point parsing: expected second number but got" + parser.currentToken());
216+
}
217+
double lat = parser.doubleValue();
218+
GeoPoint point = new GeoPoint();
219+
point.reset(lat, lon);
220+
geoPoints.add(point);
221+
} else {
222+
GeoPoint point = new GeoPoint();
223+
GeoUtils.parseGeoPoint(parser, point);
224+
geoPoints.add(point);
225+
}
226+
227+
}
228+
}
193229
}

0 commit comments

Comments
 (0)