지리공간 검색은 이제 Couchbase Server 5.5에서 완전히 지원됩니다. 자세한 내용은 Couchbase Server 5.5 발표및 개발자 빌드 다운로드 지금 무료로 이용하세요.
이 글에서는 검색을 수행하는 웹 기반 UI를 만들어 Couchbase 전체 텍스트 검색의 지리적 공간 기능을 시연해 보겠습니다. 저는 지리공간 검색을 떠올릴 때마다 특정 지역의 레스토랑을 찾는 데 탁월한 Yelp를 떠올리곤 합니다.
그래서 저는 약간의 재미를 느껴서 호텔 전용으로 아주 기본적인 버전의 Yelp를 만들어 보려고 합니다. 또한 다음을 읽어보세요. 지리공간 데이터란 무엇인가요? 처음 사용하는 경우
따라하고 싶다면 전체 소스 코드는 깃허브에서 확인할 수 있습니다..
설정하기
코드 작성을 시작하기 전에 새 프로젝트를 만들기 위해 취한 단계는 다음과 같습니다.
- 명령줄에서:
닷넷 뉴 아우렐리아. 여기에는 다음이 있다고 가정합니다. .NET Core 설치되었습니다. 지오스페이스 검색은 .NET 전용 기능이 아니므로 기타 카우치베이스 SDK Node.js, Java 등입니다. 또한 다음과 같이 설치했다고 가정합니다. Aurelia용 SPA 템플릿. 원한다면 앵귤러나 리액트를 사용할 수도 있지만, 저는 정말로 Aurelia한 번 시도해 보시는 것도 좋을 것 같습니다. - 위의 명령은 ASP.NET Core 프로젝트의 셸을 생성합니다. 이 블로그 게시물에서는 Razor를 사용하지 않겠습니다. REST API 엔드포인트의 백엔드로 ASP.NET을 사용할 뿐입니다.
npm 설치 aurelia-google-maps. 이 기능을 사용할 필요는 없지만 aurelia-google-maps 플러그인 를 사용하면 앱에서 Google 지도와 쉽게 상호 작용할 수 있습니다.- Visual Studio 2017에서 이 프로젝트를 열었습니다. 저는 Couchbase.Extensions.DependencyInjection 를 사용하세요. 이 확장 프로그램을 사용할 필요는 없지만 더 쉽게 사용할 수 있습니다.
- Couchbase Server 5.5를 설치했습니다, 포함 전체 텍스트 검색 서비스. 나는 여행용 샘플 버킷. I 사용자 생성 해당 버킷에 대한 전체 액세스 권한이 있는 "매트"로 설정합니다.
지리공간 인덱스 만들기
ASP.NET 백엔드를 구축하기 전에 Couchbase Server에서 지리공간 인덱스를 만들어야 합니다. 로그인한 후 메뉴에서 "검색"을 클릭합니다("워크벤치" 아래에 있음). "색인 추가"를 클릭하여 시작합니다.

인덱스 이름을 "mygeoindex"로 지정했습니다. 색인할 버킷으로 travel-sample을 선택했습니다.
"유형 매핑"에서 기본값을 선택 취소합니다. 유형 이름이 "hotel"인 새 유형 매핑을 추가합니다. "travel-sample"의 모든 호텔 문서에는 "hotel" 값이 있는 유형이 있습니다. "지정된 필드만 색인" 상자를 선택합니다.
두 개의 하위 필드를 추가하겠습니다. 하나는 호텔 문서 내의 지리적 공간 좌표를 포함하는 "geo"입니다. 유형으로 "지오포인트"를 선택해야 합니다. 다른 하나는 호텔 이름이 될 "name"입니다. 각각을 "저장"하기로 선택하면 색인 크기가 커지지만 색인에 정보를 저장하면 2차 조회를 피할 수 있습니다.
중요 참고: 버그(NCBC-1651)를 사용하여 지오포인트 필드에서 읽으려고 하면 오류가 발생하는 문제가 현재 릴리즈의 .NET SDK에서 발생하고 있습니다. 코드 샘플에서 해결 방법을 만들었습니다: 실제로 검색 색인에서 지역 및 이름 필드를 가져오지 않습니다. 대신 검색에서 반환된 문서 키를 사용하여 2차 '가져오기' 호출을 하고 전체 문서를 가져옵니다. 이 방법은 색인 크기를 줄이려는 경우 고려할 수 있는 기법이라는 점을 기억하세요. 이 버그는 이미 수정되었으며 향후 릴리스에 포함될 예정입니다. 최첨단에 있다는 것의 위험성이 바로 이런 것입니다!
여기까지입니다. "색인 생성"을 클릭합니다. 다음 화면에서 100%가 될 때까지 "인덱싱 진행률"을 지켜봅니다(기본값을 선택 취소한 것을 기억하고 있다면 그리 오래 걸리지 않을 것입니다).
ASP.NET 핵심 REST 엔드포인트
다음으로 ASP.NET으로 넘어가 보겠습니다. 두 개의 엔드포인트를 만들겠습니다. 한 엔드포인트는 바운딩 박스 검색 방법과 다른 하나는 거리 검색 방법.
쿼리를 실행하려면 Couchbase 버킷 객체가 필요합니다. 쿼리를 실행하려면 의존성 주입에 대한 블로그 게시물의 예제 를 참조하거나 이 작업을 해본 적이 없다면 Github에서 소스 코드를 확인하세요.
바운딩 박스
'경계 상자' 검색은 지도에서 상자를 정의하고 그 상자 안에 있는 관심 지점을 검색하는 것을 의미합니다. 상자를 정의하려면 오른쪽 상단 모서리 좌표와 왼쪽 하단 모서리 좌표, 두 점만 있으면 됩니다. (좌표는 위도와 경도입니다.)
|
1 2 3 4 5 6 7 |
public class BoxSearch { public double LatitudeTopLeft { get; set; } public double LongitudeTopLeft { get; set; } public double LatitudeBottomRight { get; set; } public double LongitudeBottomRight { get; set; } } |
경계 상자 지리공간 쿼리를 만들려면 지오바운딩 박스 쿼리 클래스를 사용할 수 있습니다. 위의 POST 메서드 내부에서 이 작업을 수행하겠습니다. BoxSearch 클래스를 매개변수로 지정합니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[Route("api/Box")] [HttpPost] public IActionResult Box([FromBody] BoxSearch box) { var query = new GeoBoundingBoxQuery(); query.TopLeft(box.LongitudeTopLeft, box.LatitudeTopLeft); query.BottomRight(box.LongitudeBottomRight, box.LatitudeBottomRight); var searchParams = new SearchParams() // .Fields("geo", "name") // omitting because of bug NCBC-1651 .Limit(10) .Timeout(TimeSpan.FromMilliseconds(10000)); var searchQuery = new SearchQuery { Query = query, Index = "mygeoindex", SearchParams = searchParams }; var results = _bucket.Query(searchQuery); // ... snip ... |
이 엔드포인트에서 반환해야 하는 것은 각 호텔의 좌표와 호텔 이름 및 위치 등 결과 목록뿐입니다. 저는 지오서치 결과 클래스입니다.
|
1 2 3 4 5 6 7 8 9 10 11 |
public class GeoSearchResult { public double Latitude { get; set; } public double Longitude { get; set; } public InfoWindow InfoWindow { get; set; } } public class InfoWindow { public string Content { get; set; } } |
이 클래스는 나중에 사용할 Google 지도 플러그인과 일치하도록 구성했습니다.
마지막으로 이 클래스를 사용하여 엔드포인트에서 몇 가지 결과를 반환하겠습니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// ... snip ... var list = new List<GeoSearchResult>(); foreach (var hit in results.Hits) { // *** this part shouldn't be necessary // the geo and name should come with the search results // but there's an SDK bug NCBC-1651 var doc = _bucket.Get<dynamic>(hit.Id).Value; // **************** list.Add(new GeoSearchResult { Latitude = doc.geo.lat, Longitude = doc.geo.lon, InfoWindow = new InfoWindow { Content = doc.name + "<br />" + doc.city + ", " + doc.state + " " + doc.country } }); } return Ok(list); } |
거리 검색
'거리' 검색은 지리공간 쿼리를 수행하는 또 다른 방법입니다. 이번에는 상자 대신 원과 같은 형태입니다. 단일 좌표와 거리를 입력하면 됩니다. 거리는 해당 지점에서의 반경이 됩니다.
|
1 2 3 4 5 6 7 8 |
public class PointSearch { public double Latitude { get; set; } public double Longitude { get; set; } public int Distance { get; set; } // miles is being assumed as the unit public string DistanceWithUnits => Distance + "mi"; } |
기본값은 마일로 설정되어 있지만, 킬로미터를 대신 사용하거나 UI에 옵션을 표시할 수도 있습니다.
엔드포인트는 바운딩 박스 엔드포인트와 매우 유사하지만, 다음을 사용한다는 점을 제외하면 지리적 거리 쿼리.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
[Route("api/Point")] [HttpPost] public IActionResult Point([FromBody] PointSearch point) { var query = new GeoDistanceQuery(); query.Latitude(point.Latitude); query.Longitude(point.Longitude); query.Distance(point.DistanceWithUnits); var searchParams = new SearchParams() // .Fields("geo", "name") // omitting because of bug NCBC-1651 .Limit(10) .Timeout(TimeSpan.FromMilliseconds(10000)); var searchQuery = new SearchQuery { Query = query, Index = "mygeoindex", SearchParams = searchParams }; var results = _bucket.Query(searchQuery); var list = new List<GeoSearchResult>(); foreach (var hit in results.Hits) { // *** this part shouldn't be necessary // the geo and name should come with the search results // but there's an SDK bug NCBC-1651 var doc = _bucket.Get<dynamic>(hit.Id).Value; // **************** list.Add(new GeoSearchResult { Latitude = doc.geo.lat, Longitude = doc.geo.lon, InfoWindow = new InfoWindow { Content = doc.name + "<br />" + doc.city + ", " + doc.state + " " + doc.country } }); } return Ok(list); } |
이 시점에서 원하는 경우 Postman 또는 Fiddler로 이러한 엔드포인트 테스트를 시작할 수 있습니다. 하지만 이를 지도에서 확인하면 훨씬 더 좋을 것입니다.
아우렐리아 및 Google 지도
Aurelia에서는 두 가지 구성 요소, 즉 geosearchbox와 geosearchpoint를 만들었습니다.

각 지도에는 사용자가 상호 작용할 수 있는 Google 지도 구성 요소가 있습니다. 이 지도는 '여행 샘플'에 있는 많은 호텔이 있는 샌프란시스코를 중심으로 표시될 것입니다.
바운딩 박스 검색 구성 요소
그리고 google-map` 컴포넌트에는 사용자가 지도를 클릭할 때마다 실행되는 map-click.delegate가 있습니다. geosearchbox.html에서:
|
1 2 3 4 5 6 7 8 |
<google-map if.bind="markers" map-click.delegate="clickMap($event)" latitude="37.780986253433895" longitude="-122.45291600632277" zoom="12" markers.bind="markers"> </google-map> |
마커 는 단순히 지도에 표시되어야 하는 검색 결과의 좌표를 포함하는 배열입니다. 처음에는 비어 있습니다.
사용자가 지도를 처음 클릭하면 양식의 첫 번째 좌표(왼쪽 상단)가 설정됩니다. geosearchbox.ts에서
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public clickMap(event : any) { var latLng = event.detail.latLng, lat = latLng.lat(), lng = latLng.lng(); // only update top left if it hasn't been set yet // or if bottom right is already set if (!this.longitudeTopLeft || this.longitudeBottomRight) { this.longitudeTopLeft = lng; this.latitudeTopLeft = lat; this.longitudeBottomRight = null; this.latitudeBottomRight = null; } else { this.longitudeBottomRight = lng; this.latitudeBottomRight = lat; } } |
그런 다음 지도에서 다른 지점을 클릭합니다. 그러면 두 번째 좌표(오른쪽 하단)가 설정됩니다.
제 구현은 매우 기초적인 수준입니다. 멋진 그래픽도 없고 두 번째 좌표가 첫 번째 좌표의 오른쪽 하단에 있다는 검증도 없습니다. 양식의 필드는 단순히 위도와 경도로 채워집니다. geosearchbox.html에서
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<p> Bounding box search: <br /> Latitude (top left): <input type="text" value="${ latitudeTopLeft }" /> Longitude (top left): <input type="text" value="${ longitudeTopLeft }" /> <br /> Latitude (bottom right): <input type="text" value="${ latitudeBottomRight }" /> Longitude (bottom right): <input type="text" value="${ longitudeBottomRight }" /> <br /> <input if.bind="latitudeTopLeft && latitudeBottomRight" click.trigger="searchClick()" type="button" name="search" value="Search" /> </p> |
두 좌표를 선택하면 검색 버튼이 나타납니다. 이를 클릭하면 앞서 만든 엔드포인트에 이 좌표를 게시할 수 있습니다. searchClick() 메서드에서 볼 수 있습니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public searchClick() { let boxSearch = { latitudeTopLeft: this.latitudeTopLeft, longitudeTopLeft: this.longitudeTopLeft, latitudeBottomRight: this.latitudeBottomRight, longitudeBottomRight: this.longitudeBottomRight }; console.log("POSTing to api/Box: " + JSON.stringify(boxSearch)); this.http.fetch('api/Box', { method: "POST", body: json(boxSearch) }) .then(result => result.json() as Promise<any[]>) .then(data => { this.markers = data; }); } |
Aurelia, Google Maps, ASP.NET Core, Couchbase가 모두 함께 작동하면 다음과 같이 보입니다:

거리 검색
'거리' 지리공간 쿼리를 구현하는 방법은 경계 상자 UI와 유사합니다. 이번에는 지도에서 단일 지점만 클릭하면 됩니다. 하지만 거리(마일 단위)를 입력해야 합니다.
그리고 구글 지도 컴포넌트는 동일하게 보입니다. 컴포넌트의 클릭맵 함수는 다릅니다:
|
1 2 3 4 5 6 7 8 |
public clickMap(event: any) { var latLng = event.detail.latLng, lat = latLng.lat(), lng = latLng.lng(); this.longitude = lng; this.latitude = lat; } |
거리(마일 단위)를 지정한 다음 '검색'을 클릭하여 앞서 작성한 엔드포인트에 POST 요청을 합니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
geosearchbox.html: <p> Distance search: <br /> Latitude: <input type="text" value="${ latitude }" /> Longitude: <input type="text" value="${ longitude }" /> <br /> Distance (miles): <input type="text" value="${ distance }" /> <br /> <input if.bind="latitude" click.trigger="searchClick()" type="button" name="search" value="Search" /> </p> geosearchbox.ts: public searchClick() { let pointSearch = { latitude: this.latitude, longitude: this.longitude, distance: this.distance }; console.log("POSTing to api/Point: " + JSON.stringify(pointSearch)); this.http.fetch('api/Point', { method: "POST", body: json(pointSearch) }) .then(result => result.json() as Promise<any[]>) .then(data => { this.markers = data; }); } } |
아래는 동작 중인 검색의 클립입니다. 좌표를 이동하면서 결과가 어떻게 달라지는지 확인하세요.

요약
Couchbase의 내장형 지리공간 인덱싱 검색 기능을 사용하면 모든 계산과 검색이 Couchbase 데이터 플랫폼에 위임됩니다. 따라서 여러분은 (어쨌든 저보다 더 나은) 멋진 UI와 견고한 비즈니스 로직을 구축하는 데 집중할 수 있습니다.
다음을 수행해야 합니다. 문서 확인 를 참조하여 Couchbase의 지리공간 기능에 대한 전체 개요를 확인하세요. 이 블로그 게시물을 읽고 다음에 대해 자세히 알아보세요. 공간 데이터베이스.
도움이 필요하거나 궁금한 점이 있으면 다음을 확인하세요. 카우치베이스 서버 포럼에서 확인할 수 있으며, Couchbase .NET SDK에 대해 궁금한 점이 있는 경우 .NET SDK 포럼.
저와 연락하고 싶으시면 댓글을 남기거나 다음에서 저를 찾아주세요. 트위터 @mgroves.