Elasticsearch Geo-Point

A geo-point is a single latitude/longitude point on the Earth’s surface. Geo-points can be used to calculate distance from a point, to determine whether a point falls within a bounding box, or in aggregations.

Geo-points cannot be automatically detected with dynamic mapping. Instead, geo_point fields should be mapped explicitly.

PUT /elasticsearch_learning
{
   "mappings": {
     "addresses": { 
       "properties": {
        "location": {
           "type": "geo_point" 
         }
      } 
    }
  } 
}

With the location field defined as a geo_point, we can proceed to index documents containing latitude/longitude pairs, which can be formatted as strings, arrays, or objects.

Geo Distance Filter(geo_distance)

The geo_distance filter draws a circle around the specified location and finds all documents that have a geo-point within that circle.

Find all location fields within 1 miles of the specified point.

GET elasticsearch_learning/_search 
{
"query":{
  "nested" : {
    "query" : {
      "bool" : {
        "filter" : [
          {
            "geo_distance" : {
              "addresses.location" : [
                -111.881186,
                40.414897
              ],
              "distance" : 1609.344,
              "distance_type" : "arc",
              "validation_method" : "STRICT",
              "ignore_unmapped" : false,
              "boost" : 1.0
            }
          }
        ],
        "adjust_pure_negative" : true,
        "boost" : 1.0
      }
    },
    "path" : "addresses",
    "ignore_unmapped" : false,
    "score_mode" : "none",
    "boost" : 1.0
  }
}
}
/**
 * https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-nested-query.html<br>
 * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-query.html
 */
@Test
void searchWithGeopoint() {

    int pageNumber = 0;
    int pageSize = 3;

    SearchRequest searchRequest = new SearchRequest(database);
    searchRequest.allowPartialSearchResults(true);
    searchRequest.indicesOptions(IndicesOptions.lenientExpandOpen());

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.from(pageNumber * pageSize);
    searchSourceBuilder.size(pageSize);
    searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
    /**
     * fetch only a few fields
     */
    searchSourceBuilder.fetchSource(new String[]{"*"}, new String[]{"cards"});
    // searchSourceBuilder.fetchSource(new FetchSourceContext(true, new String[]{"*"}, new String[]{"cards"}));
    /**
     * Query with bool
     */
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

    /**
     * Lehi skate park: 40.414897, -111.881186<br>
     * get locations/addresses close to skate park(from a radius).<br>
     * The geo_distance filter can work with multiple locations / points per document. Once a single location /
     * point matches the filter, the document will be included in the filter.
     */
    boolQuery.filter(QueryBuilders.geoDistanceQuery("addresses.location").point(40.414897, -111.881186).distance(1, DistanceUnit.MILES));

    searchSourceBuilder.query(QueryBuilders.nestedQuery("addresses", boolQuery, ScoreMode.None));

    searchRequest.source(searchSourceBuilder);

    searchRequest.preference("nested-address");

    if (searchSourceBuilder.sorts() != null && searchSourceBuilder.sorts().size() > 0) {
        log.info("\n{\n\"query\":{}, \"sort\":{}\n}", searchSourceBuilder.query().toString(), searchSourceBuilder.sorts().toString());
    } else {
        log.info("\n{\n\"query\":{}\n}", searchSourceBuilder.query().toString());
    }

    try {
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        log.info("isTimedOut={}, totalShards={}, totalHits={}", searchResponse.isTimedOut(), searchResponse.getTotalShards(), searchResponse.getHits().getTotalHits().value);

        List<User> users = getResponseResult(searchResponse.getHits());

        log.info("results={}", ObjectUtils.toJson(users));

    } catch (IOException e) {
        log.warn("IOException, msg={}", e.getLocalizedMessage());
        e.printStackTrace();
    } catch (Exception e) {
        log.warn("Exception, msg={}", e.getLocalizedMessage());
        e.printStackTrace();
    }
}

Geo Bounding Box(geo_bounding_box) Filter

This is by far the most efficient geo-filter because its calculation is very simple. You provide it with the top, bottom, left, and right coordinates of a rectangle, and all it does is compare the latitude with the left and right coordinates, and the longitude with the top and bottom coordinates.

GET elasticsearch_learning/_search
{
"query":{
  "nested" : {
    "query" : {
      "bool" : {
        "filter" : [
          {
            "geo_bounding_box" : {
              "addresses.location" : {
                "top_left" : [
                  112.025029,
                  40.526588
                ],
                "bottom_right" : [
                  0.0,
                  0.0
                ]
              },
              "validation_method" : "STRICT",
              "type" : "MEMORY",
              "ignore_unmapped" : false,
              "boost" : 1.0
            }
          }
        ],
        "adjust_pure_negative" : true,
        "boost" : 1.0
      }
    },
    "path" : "addresses",
    "ignore_unmapped" : false,
    "score_mode" : "none",
    "boost" : 1.0
  }
}
}
/**
     * https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-nested-query.html<br>
     * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-query.html
     */
    @Test
    void searchWithGeoBoundingBox() {

        int pageNumber = 0;
        int pageSize = 3;

        SearchRequest searchRequest = new SearchRequest(database);
        searchRequest.allowPartialSearchResults(true);
        searchRequest.indicesOptions(IndicesOptions.lenientExpandOpen());

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.from(pageNumber * pageSize);
        searchSourceBuilder.size(pageSize);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        /**
         * fetch only a few fields
         */
        searchSourceBuilder.fetchSource(new String[]{"id","firstName","lastName","addresses.street","addresses.city","addresses.zipcode"}, new String[]{"cards"});
        /**
         * Query with bool
         */
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        /**
         * topLeft: herriman<br>
         * bottomRight: american folk
         */
        boolQuery.filter(QueryBuilders.geoBoundingBoxQuery("addresses.location")
                .setCorners(new GeoPoint(40.526588, 112.025029), new GeoPoint(0.0, 0.0)));

        searchSourceBuilder.query(QueryBuilders.nestedQuery("addresses", boolQuery, ScoreMode.None));

        searchRequest.source(searchSourceBuilder);

        searchRequest.preference("nested-address");

        if (searchSourceBuilder.sorts() != null && searchSourceBuilder.sorts().size() > 0) {
            log.info("\n{\n\"query\":{}, \"sort\":{}\n}", searchSourceBuilder.query().toString(), searchSourceBuilder.sorts().toString());
        } else {
            log.info("\n{\n\"query\":{}\n}", searchSourceBuilder.query().toString());
        }

        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            log.info("isTimedOut={}, totalShards={}, totalHits={}", searchResponse.isTimedOut(), searchResponse.getTotalShards(), searchResponse.getHits().getTotalHits().value);

            List<User> users = getResponseResult(searchResponse.getHits());

            log.info("results={}", ObjectUtils.toJson(users));

        } catch (IOException e) {
            log.warn("IOException, msg={}", e.getLocalizedMessage());
            e.printStackTrace();
        } catch (Exception e) {
            log.warn("Exception, msg={}", e.getLocalizedMessage());
            e.printStackTrace();
        }
    }

Scoring by Distance

It may be that distance is the only important factor in deciding the order in which results are returned, but more frequently we need to combine distance with other factors, such as full-text relevance, popularity, and price.

In these situations, we should reach for the function_score query that allows us to blend all of these factors into an overall score. 

The other drawback of sorting by distance is performance: the distance has to be cal‐ culated for all matching documents. The function_score query, on the other hand, can be executed during the rescore phase, limiting the number of calculations to just the top n results.

 




Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *