Post List

2015년 8월 8일 토요일

MongoDB Study #12 Index 실습 Part.2 2D Index

1. GeoSpatial Index

좌표로 구성되는 2D Index 입니다.
하나의 Collection에는 하나의 2D Index만 구성할 수 있습니다.

요즘 SNS나 스마트폰용 App에서 자신의 위치에서 가까운 주유소 찾기 같은 기능을 편리하게 구현할 수 있습니다.

먼저 2D 값을 가진 Collection을 하나 생성해 보겠습니다.

for (var i = 0 ; i < 100 ; i ++) db.spatial.insert( { pos : [ i % 10 , Math.floor( i / 10 ) ] } )
WriteResult({ "nInserted" : 1 })

db.spatial.createIndex( { pos : "2d" } )
{
        "createdCollectionAutomatically" : true,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}

db.spatial.getIndexes()
[
        ...
        {
                "v" : 1,
                "key" : {
                        "pos" : "2d"
                },
                "name" : "pos_2d",
                "ns" : "test.spatial"
        }
]

db.spatial.find()
{ "_id" : ObjectId("55c3e2142b0ae13631c7f0a5"), "pos" : [ 0, 0 ] }
{ "_id" : ObjectId("55c3e2142b0ae13631c7f0a6"), "pos" : [ 1, 0 ] }
...
{ "_id" : ObjectId("55c3e2142b0ae13631c7f107"), "pos" : [ 8, 9 ] }
{ "_id" : ObjectId("55c3e2142b0ae13631c7f108"), "pos" : [ 9, 9 ] } 

1.1 $near 비교 연산자

주어진 좌표에서 가장 가까운 좌표를 검색합니다.
좀 더 정확히 설명하자면 가장 가까운 좌표부터 Sorting 하여 보여줍니다.
.limit(n) 와 함께 사용하면 가장 가까운 좌표 몇개를 보여주는 식으로 활용을 할 수 있습니다.

db.spatial.find( { pos : { $near : [ 5, 5] } } ).limit(5)
{ "_id" : ObjectId("55c3e2142b0ae13631c7f0dc"), "pos" : [ 5, 5 ] }
{ "_id" : ObjectId("55c3e2142b0ae13631c7f0db"), "pos" : [ 4, 5 ] }
{ "_id" : ObjectId("55c3e2142b0ae13631c7f0e6"), "pos" : [ 5, 6 ] }
{ "_id" : ObjectId("55c3e2142b0ae13631c7f0d2"), "pos" : [ 5, 4 ] }
{ "_id" : ObjectId("55c3e2142b0ae13631c7f0dd"), "pos" : [ 6, 5 ] }

1.2 $center 비교 연산자

해당 좌표(x , y) 기준으로 해당 거리(r) 안에 있는 좌표들을 검색합니다.
(x,y) 에서 r의 반지름을 가진 원을 그려서 그 안에 있는 좌표들을 보여준다고 생각하시면 머리로 그림이 그려질 것입니다.

db.spatial.find( { pos : { $within : { $center : [ [ 3.7 , 7.6] , 2.5] } } }, { _id : 0 } )
{ "pos" : [ 2, 6 ] }
{ "pos" : [ 2, 7 ] }
...
{ "pos" : [ 5, 9 ] }
{ "pos" : [ 6, 7 ] }
{ "pos" : [ 6, 8 ] }

1.3 $box 비교 연산자

2개의 점을 받아서 그 2점을 대각선으로 한 Rectangle 내부의 점들을 검색합니다.

db.spatial.find( { pos : { $within : { $box : [ [ 2 , 2] , [ 3, 4] ] } } }, { _id : 0 } )
{ "pos" : [ 2, 2 ] }
{ "pos" : [ 2, 3 ] }
{ "pos" : [ 2, 4 ] }
{ "pos" : [ 3, 2 ] }
{ "pos" : [ 3, 3 ] }
{ "pos" : [ 3, 4 ] }

1.4 $polygon 비교 연산자

여러 개의 점으로 연결된 Polygon 내부의 점들을 검색합니다.
아래 예제는 아래위로 긴 모양의 사각형에서 대놓고 완전 티나게 (2,2)를 빼고자 해당 좌표 근처에서 살짝 안으로 들어간 모양입니다.

db.spatial.find( { pos : { $within : { $polygon : [ [ 1,1] , [ 1,3] , [2,3] , [1.999,2] , [2,1] ] } } }, { _id : 0 } )
{ "pos" : [ 1, 1 ] }
{ "pos" : [ 1, 2 ] }
{ "pos" : [ 2, 1 ] }
{ "pos" : [ 1, 3 ] }
{ "pos" : [ 2, 3 ] }

1.5 $centerSphere 비교 연산자

$center 연산자와 사용법이 같습다만, GeoJSON Object에 대해서도 적용이 가능합니다.
물론 $center에서 사용한 legacy coordinate pair에도 사용이 가능합니다.
geoJSON에 대한 자세한 설명은 ( http://geojson.org/geojson-spec.html ) 를 참조하시기 바랍니다.
간단하게는 조금 뒤에 다루겠습니다.


한가지 예를 들어보겠습니다.
금요일 상암동 누리꿈스퀘어에 있는 회사 (126.8897, 37.5794)에서 퇴근 후
상암 월드컵경기장에 있는 홈플러스 (126.8982, 37.5672)에서 장을 본 후
바로 옆 CGV (126.8978, 37.5689)에서 영화를 보고
(126.9032, 37.5655)에 가서 발닦고 잤습니다.

그 4곳의 장소에서 여려 명의 사람들과 통화를 하였을 경우 통화목록이 다음과 같이 저장이 되었습니다.
db.tel_pos.save( { pno : "010-1234-5678" , pos : [ [ 126.8897 , 37.5794 ] , [ 126.8978 , 37.5689 ] ] } )
db.tel_pos.save( { pno : "010-9876-5432" , pos : [ [ 126.8897 , 37.5794 ] , [ 126.8982 , 37.5672 ] , [ 126.9032 , 37.5655 ] ] } )
db.tel_pos.save( { pno : "010-9999-8888" , pos : [ [ 126.8897 , 37.5794 ] ] } )
db.tel_pos.createIndex({ pos : "2d" } )

db.tel_pos.find()
{ "_id" : ObjectId("55c3f3a32b0ae13631c7f109"), "pno" : "010-1234-5678", "pos" : [ [ 126.8897, 37.5794 ], [ 126.8978, 37.5689 ] ] }
{ "_id" : ObjectId("55c3f4062b0ae13631c7f10a"), "pno" : "010-9876-5432", "pos" : [ [ 126.8897, 37.5794 ], [ 126.8982, 37.5672 ], [ 126.9032, 37.5655 ] ] }
{ "_id" : ObjectId("55c3f4332b0ae13631c7f10b"), "pno" : "010-9999-8888", "pos" : [ [ 126.8897, 37.5794 ] ] }
그럼 집을 기준으로 1 Mile 이내 (1/3963) 에서 통화한 사람의 목록을 검색하는 방법은 다음과 같습니다.

db.tel_pos.find( { pos : { $within : { $centerSphere : [ [ 126.9032 , 37.5655] , 1 / 3963 ] } } }, { _id : 0 , pno : 1, pos : 1 } )
{ "pno" : "010-9876-5432", "pos" : [ [ 126.8897, 37.5794 ], [ 126.8982, 37.5672 ], [ 126.9032, 37.5655 ] ] }
{ "pno" : "010-1234-5678", "pos" : [ [ 126.8897, 37.5794 ], [ 126.8978, 37.5689 ] ] }

1.6 $nearSphere 비교 연산자

$near 연산자와 사용법이 같습니다만, GeoJSON Object에 대해서도 적용이 가능합니다.
그럼 마포구청 (126.9015, 37.5660)에서 가장 가까운 곳에서 통화한 사람을 찾는 방법은 다음과 같습니다.

db.tel_pos.find( { pos : { $nearSphere : [ 126.9015 , 37.5660] } }, { _id : 0 , pno : 1, pos : 1 } ).limit(1)
{ "pno" : "010-9876-5432", "pos" : [ [ 126.8897, 37.5794 ], [ 126.8982, 37.5672 ], [ 126.9032, 37.5655 ] ] }

2. GeoMetry Index

geoJSON에 의해 표현되는 여러가지 형태 ( 선, 직선, 다각형 ) 로 생성한 Index 입니다.
MongoDB에서는 3가지 타입의 geoJSON에 대한 GeoMetry Index를 제공합니다.

 - Point 타입
{ type : "Point" , coordinates : [ 127.1058, 37.5164] } // 잠실역 좌표

 - LineString 타입
{ type : "LineString" , coordinates : [ [ 127.1058, 37.5164] , [127.0847, 37.5105] } // 잠실역 <-> 삼성역

 - Polygon 타입
{ type : "Polygon" , coordinates : [ [ [127.1261, 37.5191] , [127.1220, 37.5221] , [127.1225, 37.5240] , [127.1270, 37.5231] , [127.1290, 37.5180] , [127.1240, 37.5117] , [127.1261, 37.5191] ] ] } // 올림픽공원을 둘러싼 7각 Polygon

2.1 Point Type

먼저 예제로 사용할 collection을 입력하겠습니다.

db.pos.insert( { _id : "m01" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.1058, 37.5164] } , name : [ "name=잠실역 2호선" , "trans_type=지하철" ] } )
db.pos.insert( { _id : "m02" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.0980, 37.5301] } , name : [ "name=동서울 터미널" , "trans_type=버스" ] } ) )
db.pos.insert( { _id : "m03" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.0952, 37.5398] } , name : [ "name=강변역 2호선" , "trans_type=지하철" ] } )
db.pos.insert( { _id : "m04" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.0742, 37.5419] } , name : [ "name=건대역 2호선" , "trans_type=지하철" ] } )
db.pos.createIndex( { loc : "2dsphere" } )


db.pos.find()
{ "_id" : "m01", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.1058, 37.5164 ] }, "name" : [ "name=잠실역 2호선", "trans_type=지하철" ] }
{ "_id" : "m02", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.098, 37.5301 ] }, "name" : [ "name=동서울 터미널", "trans_type=버스" ] }
{ "_id" : "m03", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0952, 37.5398 ] }, "name" : [ "name=강변역 2호선", "trans_type=지하철" ] }
{ "_id" : "m04", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0742, 37.5419 ] }, "name" : [ "name=건대역 2호선", "trans_type=지하철" ] }

잠실역을 기준으로 2000m (2 Km) 이내의 Point를 검색하는 방법은 다음과 같습니다.

db.pos.find( { loc : { $near : { $geometry : { type : "Point" , coordinates : [ 127.1058, 37.5164] } , $maxDistance: 2000 } } } ).pretty()
{
        "_id" : "m01",
        "data_type" : NumberLong(1),
        "loc" : {
                "type" : "Point",
                "coordinates" : [
                        127.1058,
                        37.5164
                ]
        },
        "name" : [
                "name=잠실역 2호선",
                "trans_type=지하철"
        ]
}
{
        "_id" : "m02",
        "data_type" : NumberLong(1),
        "loc" : {
                "type" : "Point",
                "coordinates" : [
                        127.098,
                        37.5301
                ]
        },
        "name" : [
                "name=동서울 터미널",
                "trans_type=버스"
        ]
}

2.2 LineString Type

앞서 사용한 pos collection에 3개의 Point를 더 추가 하겠습니다.

db.pos.insert( { _id : "m05" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.0847, 37.5120] } , name : [ "name=신천역 2호선" , "trans_type=지하철" ] } )
db.pos.insert( { _id : "m06" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.0740, 37.5133] } , name : [ "name=종합운동장역 2호선" , "trans_type=지하철" ] } )
db.pos.insert( { _id : "m07" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.0848, 37.5105] } , name : [ "name=삼성역 2호선" , "trans_type=지하철" ] } ) 


db.pos.find()
{ "_id" : "m01", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.1058, 37.5164 ] }, "name
" : [ "name=잠실역 2호선", "trans_type=지하철" ] }
{ "_id" : "m02", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.098, 37.5301 ] }, "name"
 : [ "name=동서울 터미널", "trans_type=버스" ] }
{ "_id" : "m03", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0952, 37.5398 ] }, "name
" : [ "name=강변역 2호선", "trans_type=지하철" ] }
{ "_id" : "m04", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0742, 37.5419 ] }, "name
" : [ "name=건대역 2호선", "trans_type=지하철" ] }
{ "_id" : "m05", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0847, 37.512 ] }, "name"
 : [ "name=신천역 2호선", "trans_type=지하철" ] }​

{ "_id" : "m06", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.074, 37.5133 ] }, "name"
 : [ "name=종합운동장역 2호선", "trans_type=지하철" ] }
{ "_id" : "m07", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0848, 37.5105 ] }, "name
" : [ "name=삼성역 2호선", "trans_type=지하철" ] }

잠실역 - 신천역 - 종합운동장역 - 삼성역을 지나는 선분에 인접(Intersect) 한 geoJSON을 찾는 방법은 다음과 같습니다.

db.pos.find( { loc : { $geoIntersects : { $geometry : { type : "LineString" , coordinates : [ [127.1058, 37.5164], [127.0847, 37.5120], [127.0740, 37.5133], [127.0848, 37.5105] ] } } } } ).pretty()
{
        "_id" : "m06",
        "data_type" : NumberLong(1),
        "loc" : {
                "type" : "Point",
                "coordinates" : [
                        127.074,
                        37.5133
                ]
        },
        "name" : [
                "name=종합운동장역 2호선",
                "trans_type=지하철"
        ]
}
{
        "_id" : "m01",
        "data_type" : NumberLong(1),
        "loc" : {
                "type" : "Point",
                "coordinates" : [
                        127.1058,
                        37.5164
                ]
        },
        "name" : [
                "name=잠실역 2호선",
                "trans_type=지하철"
        ]
}
{
        "_id" : "m07",
        "data_type" : NumberLong(1),
        "loc" : {
                "type" : "Point",
                "coordinates" : [
                        127.0848,
                        37.5105
                ]
        },
        "name" : [
                "name=삼성역 2호선",
                "trans_type=지하철"
        ]
}

2.3 Polygon Type

2개의 Point를 더 추가하겠습니다.

db.pos.insert( { _id : "m08" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.1261, 37.5141] } , name : [ "name=올림픽 수영장" , "trans_type=Public Sport" ] } )
db.pos.insert( { _id : "m09" , data_type : NumberLong(1), loc : { type : "Point" , coordinates : [127.1225, 37.5240] } , name : [ "name=카페" , "trans_type=Resturant" ] } )


db.pos.find()
{ "_id" : "m01", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.1058, 37.5164 ] }, "name
" : [ "name=잠실역 2호선", "trans_type=지하철" ] }
{ "_id" : "m02", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.098, 37.5301 ] }, "name"
 : [ "name=동서울 터미널", "trans_type=버스" ] }
{ "_id" : "m03", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0952, 37.5398 ] }, "name
" : [ "name=강변역 2호선", "trans_type=지하철" ] }
{ "_id" : "m04", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0742, 37.5419 ] }, "name
" : [ "name=건대역 2호선", "trans_type=지하철" ] }
{ "_id" : "m05", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0847, 37.512 ] }, "name"
{ "_id" : "m06", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.074, 37.5133 ] }, "name"
 : [ "name=종합운동장역 2호선", "trans_type=지하철" ] }
{ "_id" : "m07", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.0848, 37.5105 ] }, "name
" : [ "name=삼성역 2호선", "trans_type=지하철" ] }
 : [ "name=신천역 2호선", "trans_type=지하철" ] }
{ "_id" : "m08", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.1261, 37.5141 ] }, "name
" : [ "name=올림픽 수영장", "trans_type=Public Sport" ] }
{ "_id" : "m09", "data_type" : NumberLong(1), "loc" : { "type" : "Point", "coordinates" : [ 127.1225, 37.524 ] }, "name"
 : [ "name=카페", "trans_type=Resturant" ] }


올림픽 공원 내에 위치한 geoJSON을 찾는 방법은 다음과 같습니다.

db.pos.find( { loc : { $geoWithin : { $geometry : { type : "Polygon" , coordinates : [[ [ 127.1261, 37.5191] , [127.1220, 37.5221] , [127.1225, 37.5240] , [127.1270, 37.5231] , [127.1290, 37.5180] , [127.1240, 37.5117] , [127.1261, 37.5191]] ]} } } } ).pretty()
{
        "_id" : "m09",
        "data_type" : NumberLong(1),
        "loc" : {
                "type" : "Point",
                "coordinates" : [
                        127.1225,
                        37.524
                ]
        },
        "name" : [
                "name=카페",
                "trans_type=Resturant"
        ]
}


댓글 없음:

댓글 쓰기