1

I have 3 "places" having each a type and a location:

PUT places
{
  "mappings": {
    "test": {
      "properties": {
        "type": { "type": "keyword" },
        "location": { "type": "geo_point" }
      }
    }
  }
}

POST places/test
{
   "type" : "A",
   "location": {
      "lat": 1.378446,
      "lon": 103.763427
   }
}

POST places/test
{
   "type" : "B",
   "location": {
      "lat": 1.478446,
      "lon": 104.763427
   }
}

POST places/test
{
   "type" : "A",
   "location": {
      "lat": 1.278446,
      "lon": 102.763427
   }
}

I'd like to retrieve only one place per "type": the closest from a random position lets say "lat": 1.178446, "lon": 101.763427

In my example result answer should be composed by exactly 2 elements (one for "type: A" and one for "type: B").

I'd also prefer to avoid "aggregations" as I will need the _source of each places.

Any help would be great.

woshitom
  • 4,811
  • 8
  • 38
  • 62

1 Answers1

0

Without an aggregation, such an operation seems impossible executing one query. This can be achieved with the top-hits-aggregation.

The following has been tested with elasticsearch 6:

POST /places/_search?size=0
{
  "aggs" : {
     "group-by-type" : {
        "terms" : { "field" : "type" },
        "aggs": {
            "min-distance": {
               "top_hits": {
                  "sort": {
                    "_script": { 
                       "type": "number",
                       "script": {
                          "source": "def x = doc['location'].lat; def y = doc['location'].lon; return Math.abs(x-1.178446) + Math.abs(y-101.763427)",
                          "lang": "painless"
                       },
                      "order": "asc"
                    }
                  },
                  "_source": {
                       "includes": [ "type", "location" ]
                    },
                    "size" : 1
                 }
             }
        }
     }
  }
}

Note, I calculated the distance as:
|location.x - givenPoint.x| + |location.y - givenPoint.y|

This is the response:

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
     "total": 3,
     "max_score": 0.0,
     "hits": []
  },
  "aggregations": {
     "group-by-type": {
        "doc_count_error_upper_bound": 0,
        "sum_other_doc_count": 0,
        "buckets": [{
           "key": "A",
           "doc_count": 2,
           "min-distance": {
              "hits": {
                "total": 2,
                "max_score": null,
                   "hits": [{
                      "_index": "places",
                      "_type": "test",
                      "_id": "3",
                      "_score": null,
                      "_source": {
                         "location": {
                           "lon": 102.763427,
                           "lat": 1.278446
                         },
                         "type": "A"
                      },
                      "sort": [1.1000006934661934]
                   }]
                 }
              }
          }, {
            "key": "B",
            "doc_count": 1,
            "min-distance": {
                "hits": {
                   "total": 1,
                   "max_score": null,
                   "hits": [{
                     "_index": "places",
                     "_type": "test",
                     "_id": "2",
                     "_score": null,
                     "_source": {
                         "location": {
                            "lon": 104.763427,
                             "lat": 1.478446 
                          },
                          "type": "B"
                      },
                      "sort": [3.3000007411499093]
                   }]
                 }
               }
            }]
          }
       }
 }
Eirini Graonidou
  • 1,506
  • 16
  • 24
  • it seems like a really nice approach, im getting this error though: No field found for [location.lat] in mapping with types []. Which is weird because my place do have a location field – woshitom Mar 18 '18 at 15:28
  • can it be that location is of type -> nested? which elasticsearch version do you use? – Eirini Graonidou Mar 18 '18 at 15:49
  • Many thanks for your help, I updated my question by adding my mapping. I'm using 6.2 – woshitom Mar 18 '18 at 15:59
  • Well you only need to change the way you refer to lat and lon, because they are of type geo_point. I updated the answer. Please let me know, if it worked for you – Eirini Graonidou Mar 18 '18 at 16:10
  • many thanks it works perfectly! it's out of context but would you know if it's possible to add a "should" query inside my aggregation and order the hits inside my aggregation by the score computed by this "should"? – woshitom Mar 18 '18 at 16:52