본문 바로가기

Elasticsearch

[Elasticsearch + Node.js] 좌표 거리 구하기 with script_fields arcDistance

반응형

 

Elasticsearch db 안에 있는 데이터들의 위치와 나의 (또는 유저의) 좌표로 부터 얼마나 떨어져 있는지 거리를 나타내기 

 

실습해보기 🐸

 

1. Elasticsearch distance_test 라는 index 추가

PUT distance_test
{
  "mappings": {
    "properties": {
      "place": {
        "type": "text"
      },
      "location": {
        "type": "geo_point"
      },
      "reviews": {
        "type": "text"
      }
    }
  }
}

 

 

2. distance_test 인덱스에 데이터 추가 

POST distance_test/_doc
{
  "place": "추어탕집",
  "location": [128.123123, 34.123123]
}
POST distance_test/_doc
{
  "place": "국밥집",
  "location": [127.000003, 32.723123]
}
POST distance_test/_doc
{
  "place": "백반집",
  "location": [126.393933, 35.0100123]
}
POST distance_test/_doc
{
  "place": "제비집",
  "location": [129.123123, 35.123123]
}

 

 

3. node.js 코드 작성

 

* elasticsearch + node.js + typescript 첫 시작 시 참고글

https://blckchainetc.tistory.com/entry/Elasticsearch-Nodejs-with-Typescript-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0

 

[Elasticsearch + Node.js] with Typescript 연결하기

node.js와 TypeScript 기본 환경 설정 정리한 글 => https://blckchainetc.tistory.com/381 [TypeScript + Node.js] 새 프로젝트 시작하기 (환경 설정, nodemon, rimraf)  TypeScript + Node.js 튜토리얼 ✍🏼 1..

blckchainetc.tistory.com

 

es 에서 모든 데이터를 가져와보기!  이렇게 가져온 데이터 결과는 아래와 같다.

const run = async() => {
    const location = [127.9999, 34.9999];
    const [lon, lat] = location;

    const esData = await client.search({
        index: 'distance_test',
        body: {
            min_score: 1,
            query: {
                match_all: {}
            },
            explain: false,
        }
    })

    console.log('result =', esData);
    }

run().catch(e => {
    console.log('error=', e);
    process.exit(1);
})
result = {
  took: 1,
  timed_out: false,
  _shards: { total: 1, successful: 1, skipped: 0, failed: 0 },
  hits: {
    total: { value: 4, relation: 'eq' },
    max_score: 1,
    hits: [ [Object], [Object], [Object], [Object] ]
  }
}

 

hits.hits 안에 위에서 입력한 데이터들 4개가 있다. 그런데 이제 location 변수 (내 좌표라고 해보면) 로부터 해당 장소들의 거리를 구하려면 es에 날리는 쿼리 Body안에 script_fields 를 작성해주어야 한다. 

 

        body: {
            min_score: 1,
            query: {
                match_all: {}
            },
            script_fields: {
                geo_distance: {
                    script: {
                        params: {
                            lat, lon,
                        },
                        source: "doc['location'].arcDistance(params.lat, params.lon)",
                    }
                }
            },
            explain: false,
        }

geo_distnace 안에 script에 params {위도, 경도} 순서로 작성, source에는 해당 params가 어떻게 계산될지 작성해준다. 

* 참고 Doc https://www.elastic.co/guide/en/elasticsearch/painless/6.7/painless-api-reference.html

 

Painless API Reference | Painless Scripting Language [6.7] | Elastic

Painless API Referenceedit Painless has a strict whitelist for methods and classes to ensure all painless scripts are secure. Most of these methods are exposed directly from the Java Runtime Environment (JRE) while others are part of Elasticsearch or Painl

www.elastic.co

 

script_fields를 사용해서 가져온 데이터의 hits 를 살펴보면 그 안에 

hits of hits = [
  {
    _index: 'distance_test',
    _type: '_doc',
    _id: 'S9RQyoIBnZsIFAhkhmpp',
    _score: 1,
    fields: { geo_distance: [Array] }
  },
  {
    _index: 'distance_test',
    _type: '_doc',
    _id: 'a9RQyoIBnZsIFAhki2pc',
    _score: 1,
    fields: { geo_distance: [Array] }
  },
  {
    _index: 'distance_test',
    _type: '_doc',
    _id: 'bNRQyoIBnZsIFAhkkGoL',
    _score: 1,
    fields: { geo_distance: [Array] }
  },
  {
    _index: 'distance_test',
    _type: '_doc',
    _id: 'bdRQyoIBnZsIFAhkk2rZ',
    _score: 1,
    fields: { geo_distance: [Array] }
  }
]

이렇게 fields 가 생겨있다!  (이 전의 데이터는 _index, _type, _id, _score, _source: {} 이렇게 되어 잇음)

fields를 console.log 찍어보면, 

fields = { geo_distance: [ 98144.06142685117 ] }

이런 어떤 숫자가 나온다. 이건 해당 document안의 location과 방금 script_fields로 입력한 나의 좌표값과의 거리 차이가 m 로 나온듯하다. 

 

이제 이것을 가공해서 사용하면 된다. 

    const refinedResult = _.map(esData.hits.hits, ({_source = {}, _score, fields = {} }) => {
        const difference = _.get(fields, 'geo_distance.0', -1);
        const distance = `${(difference/1000).toFixed(3)}km`
        return {
            _source,
            score: _score,
            distance,
        }
    });

    console.log('refined result=', refinedResult);

 

엇 다시보니 데이터가 들어있는 _source가 반환되지 않았다. 찾아보니 body에 stored_fields: [ "_source" ] 를 추가해 주면 된다고 한다 ! 

    const esData = await client.search({
        index: 'distance_test',
        body: {
            min_score: 1,
            stored_fields: [
                "_source",
            ],
            query: {
                match_all: {}
            },
            .
            .
            .

 

 

결과

refined result= [
  {
    _source: { place: '추어탕집', location: [Array] },
    score: 1,
    distance: '98.144km'
  },
  {
    _source: { place: '국밥집', location: [Array] },
    score: 1,
    distance: '269.471km'
  },
  {
    _source: { place: '백반집', location: [Array] },
    score: 1,
    distance: '146.274km'
  },
  {
    _source: { place: '제비집', location: [Array] },
    score: 1,
    distance: '103.146km'
  }
]

내 좌표로부터 각각 장소의 거리 차이가 Km로 잘 나왔다. 

 

 

 

전체 코드 

const run = async() => {
    const location = [127.9999, 34.9999];
    const [lon, lat] = location;

    const esData = await client.search({
        index: 'distance_test',
        body: {
            min_score: 1,
            stored_fields: [
                "_source",
            ],
            query: {
                match_all: {}
            },
            script_fields: {
                geo_distance: {
                    script: {
                        params: {
                            lat, lon,
                        },
                        source: "doc['location'].arcDistance(params.lat, params.lon)",
                    }
                }
            },
            explain: false,
        }
    })

    console.log('result =', esData);
    console.log('hits of hits =', esData.hits.hits);
    console.log('fields =', esData.hits.hits[0].fields);

    const refinedResult = _.map(esData.hits.hits, ({_source = {}, _score, fields = {} }) => {
        const difference = _.get(fields, 'geo_distance.0', -1);
        const distance = `${(difference/1000).toFixed(3)}km`
        return {
            _source,
            score: _score,
            distance,
        }
    });

    console.log('refined result=', refinedResult);
    
    }

run().catch(e => {
    console.log('error=', e);
    process.exit(1);
})

 

반응형