Skip to content

Commit

Permalink
_geo_distance sort: allow many to many geo point distance
Browse files Browse the repository at this point in the history
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
  • Loading branch information
brwe authored and areek committed Sep 8, 2014
1 parent 2294699 commit 5af8448
Show file tree
Hide file tree
Showing 9 changed files with 477 additions and 30 deletions.
16 changes: 16 additions & 0 deletions docs/reference/search/request/sort.asciidoc
Expand Up @@ -263,6 +263,22 @@ conform with http://geojson.org/[GeoJSON].
}
--------------------------------------------------


==== Multiple reference points

Multiple geo points can be passed as an array containing any `geo_point` format, for example

```
"pin.location" : [[-70, 40], [-71, 42]]
"pin.location" : [{"lat": -70, "lon": 40}, {"lat": -71, "lon": 42}]

```
and so forth.

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.



==== Script Based Sorting

Allow to sort based on custom scripts, here is an example:
Expand Down
23 changes: 14 additions & 9 deletions src/main/java/org/elasticsearch/common/geo/GeoDistance.java
Expand Up @@ -25,6 +25,7 @@
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.fielddata.*;

import java.util.List;
import java.util.Locale;

/**
Expand Down Expand Up @@ -371,12 +372,13 @@ public double calculate(double targetLatitude, double targetLongitude) {
}
}


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

Expand All @@ -386,7 +388,7 @@ public double get(int docID) {
return 0d;
}
final GeoPoint point = singleValues.get(docID);
return distance.calculate(point.lat(), point.lon());
return distances[0].calculate(point.lat(), point.lon());
}

}, docsWithField);
Expand All @@ -396,15 +398,18 @@ public double get(int docID) {
@Override
public void setDocument(int doc) {
geoPointValues.setDocument(doc);
count = geoPointValues.count();
count = geoPointValues.count() * distances.length;
grow();
for (int i = 0; i < count; ++i) {
final GeoPoint point = geoPointValues.valueAt(i);
values[i] = distance.calculate(point.lat(), point.lon());
int valueCounter = 0;
for (FixedSourceDistance distance : distances) {
for (int i = 0; i < geoPointValues.count(); ++i) {
final GeoPoint point = geoPointValues.valueAt(i);
values[valueCounter] = distance.calculate(point.lat(), point.lon());
valueCounter++;
}
}
sort();
}

};
}
}
Expand Down
Expand Up @@ -209,7 +209,7 @@ public DistanceSource(ValuesSource.GeoPoint source, GeoDistance distanceType, or
public void setNextReader(AtomicReaderContext reader) {
final MultiGeoPointValues geoValues = source.geoPointValues();
final FixedSourceDistance distance = distanceType.fixedSourceDistance(origin.getLat(), origin.getLon(), unit);
distanceValues = GeoDistance.distanceValues(distance, geoValues);
distanceValues = GeoDistance.distanceValues(geoValues, distance);
}

@Override
Expand Down
Expand Up @@ -19,12 +19,17 @@

package org.elasticsearch.search.sort;

import org.elasticsearch.ElasticsearchParseException;
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.Arrays;
import java.util.List;
import java.util.Locale;

/**
Expand All @@ -33,10 +38,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;
Expand All @@ -61,16 +64,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(Arrays.asList(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 geohashes(String... geohashes) {
this.geohashes.addAll(Arrays.asList(geohashes));
return this;
}

Expand Down Expand Up @@ -137,13 +149,23 @@ public GeoDistanceSortBuilder setNestedPath(String nestedPath) {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("_geo_distance");

if (geohash != null) {
builder.field(fieldName, geohash);
if (geohashes.size() == 0 && points.size() == 0) {
throw new ElasticsearchParseException("No points provided for _geo_distance sort.");
}
if (geohashes.size() == 1 && points.size() == 0) {
builder.field(fieldName, geohashes.get(0));
} else if (geohashes.size() == 1 && points.size() == 0) {
builder.field(fieldName, points.get(0));
} else {
builder.startArray(fieldName).value(lon).value(lat).endArray();
builder.startArray(fieldName);
for (GeoPoint point : points) {
builder.value(point);
}
for (String geohash : geohashes) {
builder.value(geohash);
}
builder.endArray();
}

if (unit != null) {
builder.field("unit", unit);
}
Expand Down
Expand Up @@ -26,6 +26,7 @@
import org.apache.lucene.search.SortField;
import org.apache.lucene.util.FixedBitSet;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoDistance.FixedSourceDistance;
import org.elasticsearch.common.geo.GeoPoint;
Expand All @@ -43,6 +44,8 @@
import org.elasticsearch.search.internal.SearchContext;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
*
Expand All @@ -57,7 +60,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;
Expand All @@ -74,7 +77,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 }
Expand All @@ -83,7 +87,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)) {
Expand All @@ -102,14 +108,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) {
Expand All @@ -126,7 +136,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);
Expand Down Expand Up @@ -167,7 +180,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);
Expand All @@ -190,4 +203,27 @@ public double get(int docID) {

return new SortField(fieldName, geoDistanceComparatorSource, reverse);
}

private void parseGeoPoints(XContentParser parser, List<GeoPoint> geoPoints) throws IOException {
while (!parser.nextToken().equals(XContentParser.Token.END_ARRAY)) {
if (parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
// we might get here if the geo point is " number, number] " and the parser already moved over the opening bracket
// in this case we cannot use GeoUtils.parseGeoPoint(..) because this expects an opening bracket
double lon = parser.doubleValue();
parser.nextToken();
if (!parser.currentToken().equals(XContentParser.Token.VALUE_NUMBER)) {
throw new ElasticsearchParseException("geo point parsing: expected second number but got" + parser.currentToken());
}
double lat = parser.doubleValue();
GeoPoint point = new GeoPoint();
point.reset(lat, lon);
geoPoints.add(point);
} else {
GeoPoint point = new GeoPoint();
GeoUtils.parseGeoPoint(parser, point);
geoPoints.add(point);
}

}
}
}

0 comments on commit 5af8448

Please sign in to comment.