1

我需要返回Elasticsearch 数组中每个文档的多个地理点的距离。截至目前,我的结果只返回为数组计算的一个距离。

我从以下 StackOverflow 问题的代码开始: Return distance in elasticsearch results?

我的 elasticsearch 查询正文包含以下内容:

{
  "stored_fields" : [ "_source" ],
    "script_fields" : {
      "distance" : {
        "script" : {
          "inline": "doc['locations.facility.address.coordinates'].arcDistance(params.lat,params.lon) * 0.001",
          "lang": "painless",
          "params": {
            "lat": 2.27,
            "lon": 50.3
          }
        }
      }
    }
  }

而且,我的 Elasticsearch 源文档在返回时与此类似。(请注意,位置是一个数组。)

"locations": [
    {
      "facility": {
        "address": {
          "country_code": "US",
          "city": "San Diego",
          "coordinates": {
            "lon": -117.165,
            "lat": 32.8408
          },
          "country_name": "United States",
          "state_province": "California",
          "postal_code": "92123"
        }
      }
    },
    {
      "facility": {
        "address": {
          "country_code": "US",
          "city": "Tampa",
          "coordinates": {
            "lon": -82.505,
            "lat": 28.0831
          },
          "country_name": "United States",
          "state_province": "Florida",
          "postal_code": "33613"
        }
      }
    }

]

目前,我的结果返回类似于以下内容:

    "fields": {
      "distance": [
        13952.518249603361
      ]
    }

但是在距离数组中,我需要为“位置”中的每个条目返回一个值。

4

1 回答 1

0

这个很棘手。

根据文档源代码,该arcDistance方法仅适用于doc values,而不适用于这些 doc 值下的单个地理点实例

换句话说,虽然我们可以在 上进行迭代,但被迭代doc['locations.facility.address.coordinates']者并没有实现任何地理距离方法

真可惜。所以我们必须实现我们自己的地理距离函数,也许使用haversine公式

{
  "stored_fields": [
    "_source"
  ],
  "script_fields": {
    "distance": {
      "script": {
        "inline": """
          float distFrom(float lat1, float lng1, float lat2, float lng2) {
            double earthRadius = 6371000; // meters
            double dLat = Math.toRadians(lat2-lat1);
            double dLng = Math.toRadians(lng2-lng1);
            double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
                       Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                       Math.sin(dLng/2) * Math.sin(dLng/2);
            double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
            float dist = (float) (earthRadius * c);
            
            return dist;
          }
        
          return params._source.locations.stream().map(location -> {
              def lat = (float) location.facility.address.coordinates.lat;
              def lon = (float) location.facility.address.coordinates.lon;
              return distFrom(lat, lon, (float) params.lat, (float) params.lon) * 0.001;
          }).collect(Collectors.toList())
        """,
        "lang": "painless",
        "params": {
          "lat": 2.27,
          "lon": 50.3
        }
      }
    }
  }
}

屈服

"hits" : {
  ...
  "hits" : [
    {
      ...
      "_source" : {
        "locations" : [
          { ... },
          { ... }
        ]
      },
      "fields" : {
        "distance" : [
          15894.470000000001,
          13952.498
        ]
      }
    }
  ]
}

老实说,当需要进行如此多的脚本编写工作时,就会出现问题。

一般来说,应该避免使用脚本。

但更重要的是,当您不按这些地理距离进行排序时,整个计算工作应该在Elasticsearch之外完成——而是在您对搜索结果进行后处理的地方完成。例如,我使用Turf进行 javascript 地理计算。

最后,当您将多个位置/设施存储在一个数组中时,我建议使用nestedfields。它们防止数组展平,并支持有意义的排序

于 2021-01-31T20:48:04.280 回答