0

From an Elasticsearch query I'd like to retrieve all the points within a variable distance. Let say I have 2 shops, one is willing to deliver at maximum 3 km and the other one at maximum 5 km:

PUT /my_shops/_doc/1
{
    "location": {
      "lat": 40.12,
      "lon": -71.34
    },
    "max_delivery_distance": 3000
}

PUT /my_shops/_doc/2
{
    "location": {
      "lat": 41.12,
      "lon": -72.34
    },
    "max_delivery_distance": 5000
}

For a given location I'd like to know which shops are able to deliver. IE query should return shop1 if given location is within 3km and shop2 if given location is within 5km

GET /my_shops/_search
{
  "query": {
    "bool": {
      "must": {
        "match_all": {}
      },
      "filter": {
        "geo_distance": {
          "distance": max_delivery_distance,
          "location": {
            "lat": 40,
            "lon": -70
          }
        }
      }
    }
  }
}

2 Answers 2

2

There's another way to solve this without scripting (big performance hogger !!) and let ES sort it out using native Geo shapes.

I would model each document as a circle, with a center location and a (delivery) radius. First, your index mapping should look like this:

PUT /my_shops
{
  "mappings": {
    "properties": {
      "delivery_area": {
        "type": "geo_shape",
        "strategy": "recursive"
      }
    }
  }
}

Then, your documents then need to have the following form:

PUT /my_shops/_doc/1
{
  "delivery_area" : {
    "type" : "circle",
    "coordinates" : [-71.34, 40.12],
    "radius" : "3000m"
  }
}

PUT /my_shops/_doc/2
{
  "delivery_area" : {
    "type" : "circle",
    "coordinates" : [-72.34, 41.12],
    "radius" : "5000m"
  }
}

And finally the query simply becomes a geo_shape query looking at intersections between a delivery point and the delivery area of each shop.

GET /my_shops/_search
{
  "query": {
    "bool": {
      "filter": {
        "geo_shape": {
          "delivery_area": {
            "shape": {
              "type": "point",
              "coordinates": [ -70, 40 ]
            },
            "relation": "contains"
          }
        }
      }
    }
  }
}

That's it! No scripting, just geo operations.

Sign up to request clarification or add additional context in comments.

Comments

1

I think that you need to work with a script to use another field as parameter. After some research I come to this answer:

GET my_shops/_search
{
  "query": {
    "script": {
      "script": {
        "params": {
          "location": {
            "lat": 40,
            "lon": -70
          }
        },
        "source": """
      return doc['location'].arcDistance(params.location.lat, params.location.lon)/1000 <= doc['max_delivery_distance'].value"""
      }
    }
  }
}

Basically, we exploit the fact that the classes related to the GEO points are whitelisted in painless https://github.com/elastic/elasticsearch/pull/40180/ and that scripts accepts additional parameters (your fixed location).

According to the documentation of arcDistance we retrieve the size in meters, so you need to convert this value into km by dividing by 1000.

Additional Note

I assume that location and max_delivery_distance are always (for each document) defined. If it is not the case, you need to cover this case.

Reference

1 Comment

location and max_delivery_distance are indeed always defined. many thanks for your help and for your really well documented answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.