Elasticsearch 自定义地理距离过滤器

Elasticsearch custom geo distance filter

我想从 Elasticsearch 查询中检索可变距离内的所有点。 假设我有2家商店,一家愿意送货最多3公里,另一家最多送货5公里:

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
}

对于给定的地点,我想知道哪些商店可以送货。如果给定位置在 3 公里以内,IE 查询应该 return shop1 如果给定位置在 5 公里以内,IE 查询应该是 shop2

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

我认为您需要使用脚本才能将另一个字段用作参数。经过一些研究,我得出了这个答案:

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"""
      }
    }
  }
}

基本上,我们利用了这样一个事实,即与 GEO 点相关的 类 被无痛地列入白名单 https://github.com/elastic/elasticsearch/pull/40180/ 并且脚本接受额外的参数(您的固定位置)。

根据 arcDistance 的文档,我们检索以米为单位的大小,因此您需要将此值除以 1000 转换为公里。

补充说明

我假设 location 和 max_delivery_distance 总是(针对每个文档)定义的。如果不是这种情况,你需要覆盖这种情况。

参考

还有另一种无需脚本即可解决此问题的方法(性能消耗巨大!!)并让 ES 使用原生地理形状对其进行排序。

我会将每个文档建模为 circle,具有中心位置和(交付)半径。首先,您的索引映射应如下所示:

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

然后,您的文件需要具有以下格式:

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"
  }
}

最后,查询简单地变成了 geo_shape 查询,查看每个商店的送货点和送货区域之间的交叉点。

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

就是这样!没有脚本,只有地理操作。