justMe
justMe

Reputation: 2408

Spring Data Elastic Search - Sort Geo Locations by distance

Given a Geo location point I am trying to find some sites within 10 Km and sort it by the nearest to the location given.

I managed to return the list of locations within 10km but when I try to sort it I get exceptions:

I am using the following versions of:

<properties>
        <commonscollections>3.2.1</commonscollections>
        <commonslang>2.6</commonslang>
        <spring.data.elasticsearch>1.0.0.BUILD-SNAPSHOT</spring.data.elasticsearch>
        <springversion>3.2.5.RELEASE</springversion>
</properties>

The java code as follow:

public List<SiteResource> findByGeoLocation(Double longitude, Double latitude, String channelKey, String distance) {

        if(StringUtils.isEmpty(distance)){
            distance = defaultRadius;           
        }       
        GeoPoint location = new GeoPoint(latitude, longitude);
        CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("location").within(location, distance+"km"));

        criteriaQuery.addIndices(channelKey);
        criteriaQuery.addTypes("site");

        criteriaQuery.addSort(new Sort(Sort.Direction.ASC, "location"));
        List<SiteResource> sites = esTemplate.queryForList(criteriaQuery, com.company.domain.site.SiteResource.class);
        return sites;
    }

If I remove the line:

criteriaQuery.addSort(new Sort(Sort.Direction.ASC, "location"));

then the code will returns all the locations, the problem is with the sorting

The error I am getting from elasticsearch log:

[2014-03-20 13:37:02,720][DEBUG][action.search.type       ] [Smuggler II] [210a9696a28545f2bbeecf88d64fbad8_20140318_153743][4], node[dB9dCIXMRZGmdUExquMWlQ], [P], s[STARTED]: Failed to execute [org.elasticsearch.action.search.SearchRequest@71b978fe] lastShard [true]
org.elasticsearch.search.SearchParseException: [210a9696a28545f2bbeecf88d64fbad8_20140318_153743][4]: query[ConstantScore(*:*)],from[0],size[-1]: Parse Failure [Failed to parse source [{"from":0,"query":{"match_all":{}},"post_filter":{"geo_distance":{"location":[-2.217753,53.432703],"distance":"10km"}},"sort":[{"location":{"order":"asc"}}]}]]
    at org.elasticsearch.search.SearchService.parseSource(SearchService.java:595)
    at org.elasticsearch.search.SearchService.createContext(SearchService.java:498)
    at org.elasticsearch.search.SearchService.createAndPutContext(SearchService.java:472)
    at org.elasticsearch.search.SearchService.executeDfsPhase(SearchService.java:178)
    at org.elasticsearch.search.action.SearchServiceTransportAction.sendExecuteDfs(SearchServiceTransportAction.java:168)
    at org.elasticsearch.action.search.type.TransportSearchDfsQueryThenFetchAction$AsyncAction.sendExecuteFirstPhase(TransportSearchDfsQueryThenFetchAction.java:85)
    at org.elasticsearch.action.search.type.TransportSearchTypeAction$BaseAsyncAction.performFirstPhase(TransportSearchTypeAction.java:216)
    at org.elasticsearch.action.search.type.TransportSearchTypeAction$BaseAsyncAction.performFirstPhase(TransportSearchTypeAction.java:203)
    at org.elasticsearch.action.search.type.TransportSearchTypeAction$BaseAsyncAction$2.run(TransportSearchTypeAction.java:186)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1146)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:701)
Caused by: org.elasticsearch.ElasticsearchIllegalArgumentException: can't sort on geo_point field without using specific sorting feature, like geo_distance
    at org.elasticsearch.index.fielddata.plain.AbstractGeoPointIndexFieldData.comparatorSource(AbstractGeoPointIndexFieldData.java:142)
    at org.elasticsearch.search.sort.SortParseElement.addSortField(SortParseElement.java:222)
    at org.elasticsearch.search.sort.SortParseElement.addCompoundSortField(SortParseElement.java:172)
    at org.elasticsearch.search.sort.SortParseElement.parse(SortParseElement.java:80)
    at org.elasticsearch.search.SearchService.parseSource(SearchService.java:583)
    ... 11 more
[2014-03-20 13:37:02,722][DEBUG][action.search.type       ] [Smuggler II] All shards failed for phase: [dfs]

If someone tell me if it is possible to sort the locations by nearest to me I very much appreciate it.

Many Thanks in advance

Upvotes: 7

Views: 7897

Answers (3)

Abacus
Abacus

Reputation: 19421

very late to the game, but since Spring Data Elasticsearch 4.0, released last week, there is the option to define a GeoDistanceOrder for a Sort which can be passed to a repository method.

The distance values from the search result can be read from the SearchHit object that is returned.

I wrote a blog post showing how to do this in details

Upvotes: 0

Black Diamond
Black Diamond

Reputation: 513

The answer, from @justMe, helped to find the latest possibility to achieve Geo Distance search. As Filters and Queries were merged, we can instead use QueryBuilders.

This is how I achieved the results (sorted and paginated):

  public List<EsEntity> findByGeoLocationSorted(Double latitude, Double longitude,
      Double distance, String sort, Pageable pageable) {

    GeoDistanceQueryBuilder filter = QueryBuilders.geoDistanceQuery("geoPoint")
        .point(latitude, longitude).distance(distance, DistanceUnit.KILOMETERS);

    SearchQuery searchQuery = new NativeSearchQueryBuilder()
        .withQuery(filter).withPageable(pageable)
        .withSort(SortBuilders.geoDistanceSort("geoPoint", latitude, longitude).order(
            SortOrder.fromString(sort))).build();

    Page<EsEntity> page = elasticsearchRestTemplate
        .queryForPage(searchQuery, EsEntity.class);
    if (page.hasContent()) {
      return page.getContent();
    }
    return Collections.emptyList();
  }

"geoPoint" -> The GeoPoint field in elasticsearch entity.

Upvotes: 0

justMe
justMe

Reputation: 2408

Just in case someone else having the same problem we have solved our sorting problem in the following way:

public List<SiteResource> findByGeoLocation(Double longitude, Double latitude, String channelKey, Double distance) {

        if(StringUtils.isEmpty(distance)){
            distance = defaultRadius;           
        }

        GeoDistanceFilterBuilder filter = FilterBuilders.geoDistanceFilter("location").point(latitude, longitude).distance(distance, DistanceUnit.KILOMETERS);

        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withFilter(filter)
                .withSort(SortBuilders.geoDistanceSort("site.location").point(latitude, longitude).order(SortOrder.ASC)).build();

        searchQuery.addIndices(channelKey);
        searchQuery.addTypes("site");

        List<SiteResource> sites = esTemplate.queryForList(searchQuery, com.company.domain.site.SiteResource.class);
        return sites;
    }

Hope it help someone ;)

Upvotes: 8

Related Questions