DannyTheDev
DannyTheDev

Reputation: 4173

Elasticsearch Geo Distance query

I've got a list of places which have their latitude and longitude associated with them in the correct mapping of geo_point

I've also got a query successfully returning results based on geo distance which looks like this:

{
  "filter": {
    "geo_distance": {
      "distance": "30mi",
      "companies.locations": {
        "lat": "51.8801595",
        "lon": "0.577141"
      }
    }
  },
  "sort": {
    "_geo_distance": {
      "companies.locations": {
        "lat": "51.8801595",
        "lon": "0.577141"
      },
      "order": "asc",
      "unit": "mi",
      "mode": "min"
    }
  },
  "from": 0,
  "size": 500
}

So this currently returns results within 30miles of the latitude and longitude provided. And this works fine.

I'm struggling with the next step, which I'm hoping someone can point me in the right direction with.

Each place has a field called distance which is an integer. This is the maximum distance a place is willing to travel to a client. So if the distance is 20 (miles) but their latitude and longitude calculates as more than 20miles they should be excluded from the results.

The results come back like this:

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": null,
    "hits": [
      {
        "_index": "test",
        "_type": "places",
        "_id": "AUtvK2OILrMWSKLclj9Z",
        "_score": null,
        "_source": {
          "id": "1",
          "name": "Chubby Company",
          "summary": "",
          "content": "",
          "locations": [
            {
              "lat": 51.8200763,
              "lon": 0.5264076
            }
          ],
          "address": [
            {
              "addr1": "xxxx",
              "addr2": "",
              "town": "MyTown",
              "county": "Essex",
              "postcode": "XX1 2XX",
              "tel1": "01111 111111",
              "tel2": "",
              "email": null
            }
          ],
          "website": "",
          "media": {
            "logo": "",
            "image": "",
            "video": ""
          },
          "product_ids": [
            "1",
            "3",
            "2"
          ],
          "distance": "20"
        },
        "sort": [
          0.031774582056958885
        ]
      }
    ]
  }
}

The sort object is distance in miles, so the result above is 0.03 miles from the client.

I'm trying to utilize this to check against the record using result to exclude it from the results but this is where I'm falling down.

I've tried different combinations of this:

"script": {
  "script": "doc['distance'].value < doc['sort'].value"
}

which combined with the query looks like this:

{
  "filter": {
    "geo_distance": {
      "distance": "30mi",
      "companies.locations": {
        "lat": "51.8801595",
        "lon": "0.577141"
      }
    }
  },
  "sort": {
    "_geo_distance": {
      "companies.locations": {
        "lat": "51.8801595",
        "lon": "0.577141"
      },
      "order": "asc",
      "unit": "mi",
      "mode": "min"
    }
  },
  "filtered": {
    "filter": {
      "script": {
        "script": "doc['distance'].value < doc['sort'].value"
      }
    }
  },
  "from": 0,
  "size": 500
}

But i get an error of:

SearchPhaseExecutionException[Failed to execute phase [query], all shards failed ... Parse Failure [No parser for element [filtered]

Any advice would be great.

UPDATE

Trying this also fails:

{
  "filter": {
    "geo_distance": {
      "distance": "30mi",
      "companies.locations": {
        "lat": "51.8801595",
        "lon": "0.577141"
      }
    },
    "script": {
      "script": "_source.distance < sort.value"
    }
  },
  "sort": {
    "_geo_distance": {
      "companies.locations": {
        "lat": "51.8801595",
        "lon": "0.577141"
      },
      "order": "asc",
      "unit": "mi",
      "mode": "min"
    }
  },
  "from": 0,
  "size": 500
}

with

nested: ElasticsearchParseException[Expected field name but got START_OBJECT \"script\"]; }]","status":400}

Upvotes: 4

Views: 13578

Answers (1)

Travis Nelson
Travis Nelson

Reputation: 2620

I had a similar problem where I wanted to have a conditional distance for each record that had this value stored in the data(searchRadius for me). I ended up using the AndFilterBuilder and ScriptFilterBuilder classes in Java, so the AndFilterBuilder has an array of both a GeoDistanceFilterBuilder and a ScriptFilterBuilder.

{
    "from": 0,
    "size": 20,
    "query": {
        "filtered": {
            "query": {
                "match_all": {}
            },
            "filter": {
                "and": {
                    "filters": [
                        {
                            "geo_distance": {
                                "location": [
                                    -104.99230194091797,
                                    39.74000930786133
                                ],
                                "distance": "3000mi"
                            }
                        },
                        {
                            "script": {
                                "script": "doc['location'].arcDistanceInMiles(39.74000930786133, -104.99230194091797) < doc['searchRadius'].value"
                            }
                        }
                    ]
                }
            }
        }
    },
    "fields": "_source",
    "script_fields": {
        "distance": {
            "script": "doc['location'].distance(39.74000930786133, -104.99230194091797)"
        }
    },
    "sort": [
        {
            "_geo_distance": {
                "location": [
                    -104.99230194091797,
                    39.74000930786133
                ],
                "unit": "mi"
            }
        }
    ]
}

Upvotes: 4

Related Questions