KakaoAPI를 이용한 위치 검색 프로그램 만들기 [언제나 WPF]

본문 내용 중에 태그 표시에 오류로 소스 코드 내용의 일부가 누락되는 현상이 있네요. 동영상을 참고하여 올바르게 작성하시기 바랍니다.

안녕하세요. 언휴예요.

이번에는 Kakao API를 이용한 위치 검색 프로그램을 만들어 볼게요.

 

최근에 웹 크롤링, 빅 데이터라는 말을 많이 하는 것 같네요.

웹을 통해 방대한 자료를 수집하고 이를 분석하여 사용하기 위한 기술이죠.

각종 포탈 사이트나 정부 기관, 일반 기업들에서 양질의 대규모의 데이터를 접근해서 사용할 수 있게 Open API 서비스를 제공하고 있어요.

이러한 서비스를 사용하는 것은 개발 언어나 환경 및 만들려고 하는 결과물에 관계없이 비슷한 형태의 기술을 사용합니다. 실제 어렵지도 않고요.

이번에 이러한 Open API 중에 카카오 API를 이용하여 위치 검색 프로그램을 만들어 봅시다.

프로그램을 작성하기 위해 여러분은 카카오 API에 가입하고 개발할 Application을 등록한 후 키를 발급받아야 합니다. 일단 Kakao Developers 로 이동하셔서 준비를 하시기 바랍니다.

화면 배치

먼저 화면 배치를 하기로 합시다.

MainWindow의 Grid에 Row를 2개, Column을 4개 배치하고 비율을 적당하게 정할게요.

그리고 TextBlock, TextBox, Button, ListBox, WebBrowser 컨트롤을 Grid에 배치합니다. 그리고 Button와 ListBox에는 Click 이벤트 와 SelectionChanged 이벤트에 핸들러를 등록합니다.

디폴트 값은 Grid.Row와 Grid.Column이 0입니다. 배치해야 할 값으로 설정을 변경하세요.

그리고 한 칸 이상 공간을 배치하려면 Grid.ColumnSpan이나 Grid.RowSpan을 이용하세요.

MyLocale 클래스 정의

Kakao API에 위치 검색하였을 때의 결과를 표현할 MyLocale클래스를 정의합시다.

MyLocale 클래스는 멤버로 이름, 위도, 경도를 갖는 Core 형식입니다.

 

namespace 카카오_API를_이용한_위치_검색
{
    class MyLocale
    {
        internal string Name
        {
            get;
            private set;
        }
        internal double Lat
        {
            get;
            private set;
        }
        internal double Lng
        {
            get;
            private set;
        }
        internal MyLocale(string name, double lat, double lng)
        {
            Name = name;
            Lat = lat;
            Lng = lng;
        }
        public override string ToString()
        {
            return Name;
        }
    }
}

이제 Kakao API를 이용하여 검색 질의할 클래스를 만들거예요.

먼저 Kakao API에 질의하였을 때 얻게 되는 결과 샘플을 Kakao Developers 사이트에서 확인합시다.

“Kakao developers>문서>Daum 검색> 개발 가이드> 로컬> 개발 가이드>키워드로 장소 검색”을 차례로 선택하세요. (카카오 정책에 따라 접근 방식이 달라질 수 있어요.)

다음 내용은 “서울 강남구 삼성동 20Km 반경에서 카카오프렌즈 매장을 검색하였을 때 검색 Sample이라고 나와 았네요.

 

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
  "meta": {
    "same_name": {
      "region": [],
      "keyword": "카카오프렌즈",
      "selected_region": ""
    },
    "pageable_count": 14,
    "total_count": 14,
    "is_end": true
  },
  "documents": [
    {
      "place_name": "카카오프렌즈 코엑스점",
      "distance": "418",
      "place_url": "http://place.map.kakao.com/26338954",
      "category_name": "가정,생활 > 문구,사무용품 > 디자인문구 > 카카오프렌즈",
      "address_name": "서울 강남구 삼성동 159",
      "road_address_name": "서울 강남구 영동대로 513",
      "id": "26338954",
      "phone": "02-6002-1880",
      "category_group_code": "",
      "category_group_name": "",
      "x": "127.05902969025047",
      "y": "37.51207412593136"
    },
    ...
  ]
}

여기에서는 documents 항목에 있는 결과 요소들을 얻어올 거예요. 그리고 결과 요소에 place_name, x, y 값을 얻어와서 MyLocale 개체로 만들거예요.

KakaoAPI 클래스 정의

이제 Kakao API를 이용할 정적 클래스 KakaoAPI를 정의합시다.

먼저 “System.Web.Extensions” 어셈블리를 참조 추가하세요. Json 방식의 결과를 프로그램 개체로 분석하기 위해 필요합니다.

KakaoAPI 형식은 정정 클래스로 검색 요청하는 Search 메서드 하나만 멤버로 정의할 거예요.

    static class KakaoAPI
    {
        internal static List<MyLocale> Search(string query)
        {
            return null;
        }
    }

Kakao API에서 “키워드로 지역 검색”할 때 최소한 검색 url에 검색 질의를 전달해야 합니다. 이 외에 다른 인자는 여러분께서 추가해 보세요.

  • 쿼리 문자열은 “사이트 url[?인자목록]” 형태를 지닙니다.
  • “?”로 url과 인자 목록을 구분합니다.
  • 인자는 “key=value” 형태를 지니며 두 개 이상의 인자일 때 “&”로 구분합니다.
 

            string site = "https://dapi.kakao.com/v2/local/search/keyword.json";
            string rquery = string.Format("{0}?query={1}", site, query);

웹 서버에 요청을 하기 위해 WebRequest 개체를 생성합니다. 그리고 Kakao API에서는 인증을 위한 REST API 키를 헤더와 함께 전달을 요구합니다.

 

            WebRequest request = WebRequest.Create(rquery);
            string rkey = "[자신의 Kakao REST API 키]";
            string header = "KakaoAK " + rkey;
            request.Headers.Add("Authorization",header);

 

WebRequest 개체의 GetResponse 메서드를 호출하면 웹 서버에 요청하여 결과를 얻어옵니다.

 
            WebResponse response = request.GetResponse();

결과를 순차적으로 읽어가면서 분석할 거예요. 선형적으로 분석할 때 Stream 개체를 사용합니다.

 

            Stream stream = response.GetResponseStream();

Stream 개체의 내용을 원하는 형식에 맞게 읽어올 때 StreamReader 개체를 브릿지 형태로 많이 사용합니다. Kakao API에서 UTF8로 결과를 보내고 있으니 이에 맞게 StreamReader 개체를 생성할게요.

그리고 끝까지 읽어옵니다.

 
            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
            String json = reader.ReadToEnd();

이제 Json 방식으로 표현된 문자열의 내용 중에 documents 부분만 얻어올 거예요. 다른 물리 매체에 있는 것을 선형적으로 얻어오는 것을 역직렬화라 불러요. Json 방식의 결과를 소스로 역직렬화할 때 JavaScriptSerializer 형식 개체를 사용합니다.

주의할 점은 역직렬화 대상 형식을 JavaScriptSerializer 개체는 알 수 없으니 동적 개체로 역직렬화 합니다.

 

            JavaScriptSerializer js = new JavaScriptSerializer();
            dynamic dob = js.Deserialize(json);

역직렬화한 동적 개체에서 “documents” 부분만 얻어옵니다. 물론 동적 개체로 얻어옵니다.

그리고 “documents” 내에 결과 항목들을 배열로 분리합니다.

 

            dynamic docs = dob["documents"];
            object[] buf = docs;

이제 배열의 각 항목으로 MyLocale 개체를 만들어야죠.

 

            int length = buf.Length;
            for(int i=0;i<length;i++)
            {
                string lname = docs[i]["place_name"];
                double x = double.Parse(docs[i]["x"]);
                double y = double.Parse(docs[i]["y"]);
                mls.Add(new MyLocale(lname, y, x));
            }

 작성한 내용은 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;

namespace 카카오_API를_이용한_위치_검색
{
    static class KakaoAPI
    {
        internal static List<MyLocale> Search(string query)
        {
            List<MyLocale> mls = new List<MyLocale>();
            string site = "https://dapi.kakao.com/v2/local/search/keyword.json";            
            string rquery = string.Format("{0}?query={1}", site, query);
            WebRequest request = WebRequest.Create(rquery);
            string rkey = "[자신의 Kakao REST API 키]";
            string header = "KakaoAK " + rkey;
            request.Headers.Add("Authorization", header);

            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
            String json = reader.ReadToEnd();

            JavaScriptSerializer js = new JavaScriptSerializer();
            dynamic dob = js.Deserialize<object>(json);
            dynamic docs = dob["documents"];
            object[] buf = docs;
            int length = buf.Length;
            for (int i = 0; i < length; i++)
            {
                string lname = docs[i]["place_name"];
                double x = double.Parse(docs[i]["x"]);
                double y = double.Parse(docs[i]["y"]);
                mls.Add(new MyLocale(lname, y, x));
            }
            
            return mls;
        }
    }
}

MainWindow.cs 작성

이제 MainWindow.cs로 가서 버튼 클릭 이벤트 핸들러를 작성합시다.

TextBox에 입력한 내용으로 검색 요청합니다.

그리고 검색 결과를 ListBox의 ItemSource로 설정합니다.

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            List<MyLocale> mls = KakaoAPI.Search(tbox_query.Text);
            lbox_locale.ItemsSource = mls;
        }

ListBox의 선택 변경 이벤트 핸들러를 작성합시다.

선택한 항목을 MyLocale 형식 개체로 참조합니다.

그리고 위도, 경도 값으로 object 배열을 만들어서 WebBrowser 개체의 InvokeScript 메서드를 호출할 때 전달합니다. WebBrowser 개체의 InvokeScript 메서는 JavaScript 메서드를 호출할 때 사용합니다.

이미 https://ehpub.co.kr/kakaomap.html에는 setCenter라는 JavaScript 메서드를 작성해 놓았습니다.

 

        private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if(lbox_locale.SelectedIndex==-1)
            {
                return;
            }
            MyLocale ml = lbox_locale.SelectedItem as MyLocale;
            object[] ps = new object[] { ml.Lat, ml.Lng };
            wb.InvokeScript("setCenter", ps);
        }

이제 모두 작성했으니 테스트 해 보세요.

 

다음은 MainWindow.xaml 소스 코드와 kakaomap.html의 내용입니다.

MainWindow.xaml

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace 카카오_API를_이용한_위치_검색
{
    ///
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            List<MyLocale> mls = KakaoAPI.Search(tbox_query.Text);
            lbox_locale.ItemsSource = mls;
        }

        private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if(lbox_locale.SelectedIndex==-1)
            {
                return;
            }
            MyLocale ml = lbox_locale.SelectedItem as MyLocale;
            object[] ps = new object[] { ml.Lat, ml.Lng };
            wb.InvokeScript("setCenter", ps);
        }
    }
}

kakaomap.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>카카오지도</title>
    
</head>
<body>
<div id="map" style="width:100%;height:350px;"></div>

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 JavaScript 키"></script>
<script>
var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

var map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다

function setCenter(lat,lng) {            
    var mp = new kakao.maps.LatLng(lat,lng);
    map.setCenter(mp);
}
</script>
</body>
</html>