Skip to content

Latest commit

 

History

History
818 lines (741 loc) · 22.9 KB

File metadata and controls

818 lines (741 loc) · 22.9 KB
description
이 문서의 허가되지 않은 무단 복제나 배포 및 출판을 금지합니다. 본 문서의 내용 및 도표 등을 인용하고자 하는 경우 출처를 명시하고 김종민([email protected])에게 사용 내용을 알려주시기 바랍니다.

7.2.6 위치 정보 - Geo

검색엔진을 사용하는 여러 서비스들 중에 요즘은 모바일 기기들을 이용해서 위치 정보를 표시하거나 검색하는 서비스들이 많이 있습니다. Elasticsearch는 자바와 기타 프로그래밍 언어에서 제공하는 기본 데이터 타입 외에도 여러가지의 추상화 된 데이터 타입들이 있습니다. 그 중에 이런 위치정보를 저장할 수 있는 Geo PointGeo Shape 같은 타입들이 있습니다.

Geo Point

Geo Point 는 위도(latitude)와 경도(longitude) 두 개의 실수 값을 가지고 지도 위의 한 점을 나타내는 값입니다. Geo Point 필드의 값들은 다음과 같이 다양한 방법으로 입력이 가능합니다.

{% code title="object 형식으로 geo_point 입력" %}

PUT my_locations/_doc/1
{
  "location": {
    "lat": 41.12,
    "lon": -71.34
  }
}

{% endcode %}

{% code title="text 형식으로 geo_point 입력" %}

PUT my_index/_doc/2
{
  "location": "41.12,-71.34"
}

{% endcode %}

{% code title="geohash 형식으로 geo_point 입력" %}

PUT my_index/_doc/3
{
  "location": "drm3btev3e86"
}

{% endcode %}

{% code title="실수의 배열 형식으로 geo_point 입력" %}

PUT my_index/_doc/4
{
  "location": [
    -71.34,
    41.12
  ]
}

{% endcode %}

Text 와 실수 방식은 위도와 경도의 입력 순서가 서로 반대이기 때문에 헷갈리기 쉽습니다. 그래서 보통은 {"lat": 41.12, "lon": -71.34} 와 같이 알아보기 편한 object 형식으로 입력합니다.

geohash 는 전 세계 지도를 바둑판 모양의 격자로 나누어 각 칸 마다 숫자와 알파벳으로 기호를 메기고, 그 칸을 다시 나누어 다시 기호를 추가하는 방식으로 표현한 것입니다. 자릿수가 커질수록 정밀도가 높아집니다. 보통 1자리 값이면 대륙, 2자리 값이면 대한민국 영토 정도의 크기이고 4자리 값이면 대도시, 7자리 값이면 길거리 한 블록 정도의 정밀도를 나타냅니다. 아래 예시에서 sg 값은 북유럽 스칸디나비아 반도 부근의 크기입니다.

geohash 로 위치 정보 표시

Geo Point 필드는 매핑에서 다음과 같이 "type": "geo_point" 로 선언합니다.

{% code title="object 형식으로 geo_point 입력" %}

PUT my_geo
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_point"
      }
    }
  }
}

{% endcode %}

Geo Point 필드의 경우는 반드시 데이터를 입력하기 전에 인덱스 매핑을 정의 해 주어야 합니다. 매핑을 정의하지 않고 { "location": { "lat" : 41.12, "lon": -71.34 } } 와 같은 값을 입력하면 다이나믹 매핑으로 필드가 자동 생성될 때 geo_point 타입의 필드가 생기는 것이 아니라 다음과 같이 float 타입의 lat, lon 두개의 하위 필드가 생깁니다.

{% tabs %} {% tab title="request" %} {% code title="도큐먼트 입력으로 my_geo 인덱스 동적 생성 후 매핑 확인" %}

PUT my_geo/_doc/1
{
  "location": {
    "lat": 41.12,
    "lon": -71.34
  }
}

GET my_geo/_mapping

{% endcode %} {% endtab %}

{% tab title="response" %} {% code title="도큐먼트 입력으로 my_geo 인덱스 동적 생성 후 매핑 확인 결과" %}

{
  "my_geo" : {
    "mappings" : {
      "properties" : {
        "location" : {
          "properties" : {
            "lat" : {
              "type" : "float"
            },
            "lon" : {
              "type" : "float"
            }
          }
        }
      }
    }
  }
}

{% endcode %} {% endtab %} {% endtabs %}

geo_bounding_box 쿼리

Elasticsearch에는 위치정보를 검색할 수 있는 다양한 쿼리들이 있습니다. geo_point 값의 검색에 주로 사용 되는 것은 geo_bounding_box 쿼리와 geo_distance 쿼리 입니다. 예제 위해 먼저 다음 도큐먼트들을 my_geo 인덱스에 벌크로 입력하겠습니다. location 필드의 타입을 "type": "geo_point" 로 매핑을 먼저 설정해야 하는 것을 잊지 마세요.

{% code title="my_geo 인덱스에 예제 데이터 입력" %}

PUT my_geo/_bulk
{"index":{"_id":"1"}}
{"station":"강남","location":{"lon":127.027926,"lat":37.497175},"line":"2호선"}
{"index":{"_id":"2"}}
{"station":"종로3가","location":{"lon":126.991806,"lat":37.571607},"line":"3호선"}
{"index":{"_id":"3"}}
{"station":"여의도","location":{"lon":126.924191,"lat":37.521624},"line":"5호선"}
{"index":{"_id":"4"}}
{"station":"서울역","location":{"lon":126.972559,"lat":37.554648},"line":"1호선"}

{% endcode %}

geo_bounding_box 쿼리는 top_leftbottom_right 두 개의 옵션에 각각 위치점을 입력하고 이 점들을 토대로 그린 네모 칸 안에 위치하는 도큐먼트들을 불러옵니다. 다음은 {"lat": 37.4899, "lon": 127.0388}, {"lat": 37.5779, "lon": 126.9617} 두 점을 기준으로 하는 네모 영역 안에 있는 도큐먼트들을 가져오는 예제입니다.

{% tabs %} {% tab title="request" %} {% code title="geo_bounding_box 쿼리로 네모 영역 안에 있는 도큐먼트 쿼리" %}

GET my_geo/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "bottom_right": {
          "lat": 37.4899,
          "lon": 127.0388
        },
        "top_left": {
          "lat": 37.5779,
          "lon": 126.9617
        }
      }
    }
  }
}

{% endcode %} {% endtab %}

{% tab title="response" %} {% code title="geo_bounding_box 쿼리로 네모 영역 안에 있는 도큐먼트 쿼리 결과" %}

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_geo",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "station" : "강남",
          "location" : {
            "lon" : 127.027926,
            "lat" : 37.497175
          },
          "line" : "2호선"
        }
      },
      {
        "_index" : "my_geo",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "station" : "종로3가",
          "location" : {
            "lon" : 126.991806,
            "lat" : 37.571607
          },
          "line" : "3호선"
        }
      },
      {
        "_index" : "my_geo",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "station" : "서울역",
          "location" : {
            "lon" : 126.972559,
            "lat" : 37.554648
          },
          "line" : "1호선"
        }
      }
    ]
  }
}

{% endcode %} {% endtab %} {% endtabs %}

"station" : "강남", "station" : "종로3가", "station" : "서울역" 총 3개의 결과가 리턴 되었습니다. 위 쿼리 내용을 지도에 표현 해 보면 다음과 같습니다.

geo_bounding_box 쿼리 결과

geo_distance 쿼리

geo_distance 쿼리는 하나의 위치점을 찍고 distance 옵션을 이용해서 입력한 반경의 원 안에 있는 도큐먼트들을 불러옵니다. 다음은 {"lat": 37.5358, "lon": 126.9559} 기준으로 반경 5킬로미터 안에 있는 도큐먼트들을 불러오는 예제입니다.

{% tabs %} {% tab title="request" %} {% code title="geo_bounding_box 쿼리로 네모 영역 안에 있는 도큐먼트 쿼리" %}

GET my_geo/_search
{
  "query": {
    "geo_distance": {
      "distance": "5km",
      "location": {
        "lat": 37.5358,
        "lon": 126.9559
      }
    }
  }
}

{% endcode %} {% endtab %}

{% tab title="response" %} {% code title="geo_bounding_box 쿼리로 네모 영역 안에 있는 도큐먼트 쿼리 결과" %}

{
  "took" : 71,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_geo",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "station" : "여의도",
          "location" : {
            "lon" : 126.924191,
            "lat" : 37.521624
          },
          "line" : "5호선"
        }
      },
      {
        "_index" : "my_geo",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "station" : "서울역",
          "location" : {
            "lon" : 126.972559,
            "lat" : 37.554648
          },
          "line" : "1호선"
        }
      }
    ]
  }
}

{% endcode %} {% endtab %} {% endtabs %}

"station" : "여의도", "station" : "서울역" 총 2개의 결과가 리턴되었습니다. distance 값을 10km, 20km 등으로 변경해서 다시 쿼리를 해 보면 종로3가, 강남이 결과에 나타나는 것도 확인할 수 있습니다. 위 쿼리를 지도에 표시 해 보면 다음과 같습니다.

geo_distance 쿼리 결과

Geo Shape

앞에서 살펴본 Geo Point 는 위도 경도 두개의 값을 가진 1차원 데이터 "점" 입니다. Elasticsearch 에서 사용 가능한 또 다른 위치정보 타입인 Geo Shape 은 선, 면 등의 2차원 값을 저장하고 쿼리할 수 있습니다. Geo Shape 필드는 "type": "geo_shape" 으로 선언합니다.

{% code title="geo_shape 필드 선언" %}

PUT my_shape
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_shape"
      }
    }
  }
}

{% endcode %}

도큐먼트에도 점, 선, 다중점, 다중선, 다각형 등을 "type" 값에 각각 다음과 같이 지정하고 "coordinates" 값에 위치 정보를 [ -71.34, 41.12 ] 같이 [경도, 위도] 의 순서로 배열 형식으로 입력합니다. 위치정보 순서가 반대로 되면 엉뚱한 값이 되기 때문에 주의해야 합니다.

  • "type": "point" - 단일 점 입니다. 보통은 geo_point 와 같은 용도로 사용됩니다.

{% code title=""type": "point" 형태의 geo_shape 값 입력" %}

PUT my_shape/_doc/1
{
  "location": {
    "type": "point",
    "coordinates": [
      127.027926,
      37.497175
    ]
  }
}

{% endcode %}

"type": "point" 으로 선언한 값 - 점

  • "type": "multipoint" - 여러 점을 하나의 값으로 저장합니다. 점들을 배열로 입력합니다.

{% code title=""type": "multipoint" 형태의 geo_shape 값 입력" %}

PUT my_shape/_doc/2
{
  "location": {
    "type": "multipoint",
    "coordinates": [
      [ 127.027926, 37.497175 ],
      [ 126.991806, 37.571607 ],
      [ 126.924191, 37.521624 ],
      [ 126.972559, 37.554648 ]
    ]
  }
}

{% endcode %}

"type": "multipoint" 로 선언한 값 - 다중 점

  • "type": "linestring" - 점 2개 값를 배열로 입력하여 두 점을 잇는 직선을 저장합니다. 비행 경로 등을 저장할때 유용합니다.

{% code title=""type": "linestring" 형태의 geo_shape 값 입력" %}

PUT my_shape/_doc/3
{
  "location": {
    "type": "linestring",
    "coordinates": [
      [ 127.027926, 37.497175 ],
      [ 126.991806, 37.571607 ]
    ]
  }
}

{% endcode %}

"type": "linestring" 으로 선언 한 값 - 직선

  • "type": "multilinestring" - 여러개의 직선을 배열로 입력하여 저장합니다.

{% code title=""type": "multilinestring" 형태의 geo_shape 값 입력" %}

PUT my_shape/_doc/4
{
  "location": {
    "type": "multilinestring",
    "coordinates": [
      [
        [ 127.027926, 37.497175 ],
        [ 126.991806, 37.571607 ]
      ],
      [
        [ 126.924191, 37.521624 ],
        [ 126.972559, 37.554648 ]
      ]
    ]
  }
}

{% endcode %}

"type": "multilinestring" 으로 선언 한 값 - 다중 직선

  • "type": "polygon" - 다각형을 저장합니다. 내부에 배열을 추가하고 점들을 배열로 입력하며 순서대로 이어집니다. 배열 마지막에는 반드시 처음과 같은 점이 입력되어야 합니다. 영토 정보 등을 저장할때 유용합니다.

{% code title=""type": "polygon" 형태의 geo_shape 값 입력" %}

PUT my_shape/_doc/5
{
  "location": {
    "type": "polygon",
    "coordinates": [
      [
        [ 127.027926, 37.497175 ],
        [ 126.991806, 37.571607 ],
        [ 126.924191, 37.521624 ],
        [ 126.972559, 37.554648 ],
        [ 127.027926, 37.497175 ]
      ]
    ]
  }
}

{% endcode %}

"type": "polygon" 으로 선언한 값 - 다각형

  • "type": "multipolygon" - 여러 개의 다각형을 배열로 저장합니다. 미국의 알래스카, 하와이, 본토 등과 같이 분리된 영토를 같이 저장하는데 유용합니다.

{% code title=""type": "multipolygon" 형태의 geo_shape 값 입력" %}

PUT my_shape/_doc/6
{
  "location": {
    "type": "multipolygon",
    "coordinates": [
      [
        [
          [ 127.027926, 37.497175 ],
          [ 126.991806, 37.571607 ],
          [ 126.924191, 37.521624 ],
          [ 127.004943, 37.504810 ],
          [ 127.027926, 37.497175 ]
        ]
      ],
      [
        [
          [ 126.936893, 37.555134 ],
          [ 126.967894, 37.529170 ],
          [ 126.924191, 37.521624 ],
          [ 126.936893, 37.555134 ]
        ]
      ]
    ]
  }
}

{% endcode %}

"type": "multipolygon" 으로 선언한 값 - 다중 다각형

  • "type": "envelope" - 바른 각도의 직사각형 영역을 저장할때는 polygon 으로 4개의 점을 입력하여 지정할 수도 있지만, 대신 envelope 을 사용하면 좌측 상단(upper left)우측 하단(lower right) 점 두개만 이용해서 그리는 것도 가능합니다.

{% code title=""type": "envelope" 형태의 geo_shape 값 입력" %}

PUT my_shape/_doc/7
{
  "location": {
    "type": "envelope",
    "coordinates": [
      [ 126.936893, 37.555134 ],
      [ 127.004943, 37.50481 ]
    ]
  }
}

{% endcode %}

"type": "envelope" 으로 선언한 값 - 직사각형

{% hint style="warning" %} 6.5 이전 버전 까지는 한 점의 coordinates 와 "radius": "5km" 같은 반경을 지정하는 값을 이용해서 "type": "circle" 인 원 형태의 데이터도 사용이 가능했습니다. 버전6.6 부터는 geo_shape 데이터의 저장 방식이 변경되면서 6.6 버전 이후 부터 필자가 글을 쓰고 있는 현재 7.4 버전에서의 원 형태의 데이터는 지원하지 않고 있습니다. 이후 다시 구현 될 가능성은 있습니다. {% endhint %}

geo_shape 쿼리

Geo Shape 타입의 값들을 검색하려면 geo_shape 쿼리를 사용해야 합니다. 입력해야하는 옵션으로는 "shape": { } 에 검색할 영역의 "type""coordinates" 값을 입력하고, "relation" 에 검색할 영역과 검색되는 도큐먼트가 겹치거나 포함되는 관계 조건 값을 입력합니다. relation 옵션에 입력 가능한 값은 intersects, disjoint, within 3가지가 있습니다.

  • "relation": "intersects" - 디폴트 값입니다. 쿼리 영역과 도큐먼트 값 영역이 일부라도 겹쳐지면 참 입니다.
  • "relation": "disjoint" - 도큐먼트 값 영역이 쿼리 영역과 겹치지 않는 쿼리 영역 바깥에 있는 도큐먼트들을 가져옵니다.
  • "relation": "within" - 도큐먼트의 값들이 모두 쿼리 영역 안에 완전히 포함 되어 있는 도큐먼트들을 가져옵니다.

예제 실행을 위해 my_shape 인덱스에 다음 세 개의 envelope 방식의 geo_shape 값을 입력하겠습니다. 먼저 location 필드의 매핑을 "type": "geo_shape" 으로 지정해야 하는 것을 명심하세요.

{% code title="geo_shape 예제를 위한 3개의 도큐먼트 입력" %}

PUT my_shape/_doc/1
{
  "place": "경복궁",
  "location": {
    "type": "envelope",
    "coordinates": [
      [ 126.9735, 37.5837 ],
      [ 126.9802, 37.5756 ]
    ]
  }
}

PUT my_shape/_doc/2
{
  "place": "명동",
  "location": {
    "type": "envelope",
    "coordinates": [
      [ 126.9778, 37.5656 ],
      [ 126.9884, 37.5558 ]
    ]
  }
}

PUT my_shape/_doc/3
{
  "place": "홍대",
  "location": {
    "type": "envelope",
    "coordinates": [
      [ 126.9199, 37.5583 ],
      [ 126.9347, 37.5481 ]
    ]
  }
}

{% endcode %}

이제 geo_shape 쿼리를 이용해서 "top_left": {"lat": 37.5800, "lon": 126.9687}, "bottom_right": {"lat": 37.5543, "lon": 126.9900} 의 직사각형 (envelope) 범위 안에 있는 도큐먼트 들을 relation 별로 검색을 해 보겠습니다. 먼저 위에 입력한 도큐먼트와 이후 검색할 쿼리 범위를 지도에 표시하면 다음과 같습니다.

입력된 예제 도큐먼트와 쿼리 범위

다음은 geo_shape 쿼리의 relation 값을 각각 intersects, within, disjoint 한 결과입니다.

{% tabs %} {% tab title="request" %} {% code title=""relation": "intersects" 으로 geo_shape 쿼리" %}

GET my_shape/_search
{
  "query": {
    "geo_shape": {
      "location": {
        "shape": {
          "type": "envelope",
          "coordinates": [
            [ 126.9687, 37.58 ],
            [ 126.99, 37.5543 ]
          ]
        },
        "relation": "intersects"
      }
    }
  }
}

{% endcode %} {% endtab %}

{% tab title="response" %} {% code title=""relation": "intersects" 으로 geo_shape 쿼리 결과" %}

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_shape",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "place" : "경복궁",
          "location" : {
            "type" : "envelope",
            "coordinates" : [
              [
                126.9735,
                37.5837
              ],
              [
                126.9802,
                37.5756
              ]
            ]
          }
        }
      },
      {
        "_index" : "my_shape",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "place" : "명동",
          "location" : {
            "type" : "envelope",
            "coordinates" : [
              [
                126.9778,
                37.5656
              ],
              [
                126.9884,
                37.5558
              ]
            ]
          }
        }
      }
    ]
  }
}

{% endcode %} {% endtab %} {% endtabs %}

  • "relation": "intersects" - 쿼리 영역에 조금이라도 걸쳐 있는 "place" : "경복궁""place" : "명동" 도큐먼트들이 결과로 나타납니다.

{% tabs %} {% tab title="request" %} {% code title=""relation": "within" 으로 geo_shape 쿼리" %}

GET my_shape/_search
{
  "query": {
    "geo_shape": {
      "location": {
        "shape": {
          "type": "envelope",
          "coordinates": [
            [ 126.9687, 37.58 ],
            [ 126.99, 37.5543 ]
          ]
        },
        "relation": "within"
      }
    }
  }
}

{% endcode %} {% endtab %}

{% tab title="response" %} {% code title=""relation": "within" 으로 geo_shape 쿼리 결과" %}

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_shape",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "place" : "명동",
          "location" : {
            "type" : "envelope",
            "coordinates" : [
              [
                126.9778,
                37.5656
              ],
              [
                126.9884,
                37.5558
              ]
            ]
          }
        }
      }
    ]
  }
}

{% endcode %} {% endtab %} {% endtabs %}

  • "relation": "within" - 쿼리 영역에 완전히 포함되어 있는 "place" : "명동" 도큐먼트만 결과로 나타납니다.

{% tabs %} {% tab title="request" %} {% code title=""relation": "disjoint" 으로 geo_shape 쿼리" %}

GET my_shape/_search
{
  "query": {
    "geo_shape": {
      "location": {
        "shape": {
          "type": "envelope",
          "coordinates": [
            [ 126.9687, 37.58 ],
            [ 126.99, 37.5543 ]
          ]
        },
        "relation": "disjoint"
      }
    }
  }
}

{% endcode %} {% endtab %}

{% tab title="response" %} {% code title=""relation": "disjoint" 으로 geo_shape 쿼리 결과" %}

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_shape",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "place" : "홍대",
          "location" : {
            "type" : "envelope",
            "coordinates" : [
              [
                126.9199,
                37.5583
              ],
              [
                126.9347,
                37.5481
              ]
            ]
          }
        }
      }
    ]
  }
}

{% endcode %} {% endtab %} {% endtabs %}

  • "relation": "disjoint" - 쿼리 영역 바깥에 있는 "place" : "홍대" 도큐먼트가 결과로 나타납니다.

이 외에도 정사각형이 아닌 다각형으로 쿼리할 수 있는 geo_polygon 쿼리가 있습니다. 이 책에서 geo_polygon 쿼리는 다루지 않으니 공식 도큐먼트를 확인 하시기 바랍니다. 앞에서 배운 쿼리들을 이해 하셨으면 공식 도큐먼트를 참고 해서 어렵지 않게 사용이 가능 할 것입니다.