OpenAPI/C# OpenAPI

4. 경로 탐색 [경로찾기 프로젝트 – C#, 카카오 지도 API, SK TMAP API 활용 ]

언휴 2024. 1. 9. 08:38

안녕하세요. 언제나휴일입니다.

1. 유튜브 동영상 강의

 

[C#] 경로 탐색 - 카카오 지역 API + SK TMAP API

앞에서 지역 검색 예광탄에서는 카카오 개발자 센터 로컬 API를 사용했습니다.

지역 검색 및 위치 확인에서는 카카오 개발자 센터 로컬 API와 지도 API를 사용했습니다.

이번에는 경로 탐색 프로그램을 제작할 것입니다.

경로 탐색 프로그램은 카카오 개발자 센터 로컬 API와 SK TMap API를 사용합니다.

동영상 강의는  언제나 휴일 유튜브에 업로드하였습니다.

2. 제작할 프로그램 소개

경로 탐색은 C#, 콘솔 (.NET Framework) 프로젝트입니다.

프로그램을 시작하면서 출발지를 입력합니다.

카카오 개발자 센터 로컬 REST API를 이용하여 검색한 내용을 출력하면 사용자가 원하는 정보를 선택합니다.

출발지 선택
출발지 선택

목적지도 입력합니다.

목적지 선택
목적지 선택

SK TMAP API를 이용하여 출발지와 목적지 사이의 경로를 탐색하여 콘솔 화면에 출력합니다.

경로 출력
경로 출력

3. Main 로직 및 경로 선택 구현

Main 메서드에서는 출발지와 목적지를 선택하여 경로를 탐색하는 메서드를 호출합니다.

Locale은 클래스로 정의하고 지역 선택 및 경로 탐색은 메서드로 정의합니다.

 static void Main(string[] args)
 {
     Locale start = SelectLocale("출발지:");
     Locale end = SelectLocale("목적지:");
     SearchRoute(start, end);
 }
 private static void SearchRoute(Locale start, Locale end)
 {
 }

 private static Locale SelectLocale(string msg)
 {
 }

Locale은 지역 검색 및 위치 확인 프로젝트에서 만든 Locale 클래스 내용을 복붙합니다.

(물론 클래스 라이브러리로 만들어서 참조해도 좋습니다. 여기서는 만들기로 할게요.)

3.1 Locale.cs

namespace 경로_탐색
{
    public class Locale
    {
        public string Pname;
        public double Lng { get; }
        public double Lat { get; }
        public Locale(string pname, double lng, double lat)
        {
            Pname = pname;
            Lng = lng;
            Lat = lat;
        }
        public override string ToString()
        {
            return Pname;
        }
    }
}

3.2 지역 선택 SelectLocale 메서드 구현

지역 검색은 앞에서 두 개의 프로젝트에서 이미 해 본 것입니다. 일단 복 붙 한 후에 수정하기로 합시다.

질의로 웹 요청하여 응답을 얻어오는 부분까지는 특이 사항이 없습니다.

            Console.Clear();
            Console.Write(msg);
            string query = Console.ReadLine();
            string url = "https://dapi.kakao.com/v2/local/search/keyword.json";
            string query_str = string.Format("{0}?query={1}", url, query);
            WebRequest request = WebRequest.Create(query_str);
            request.Headers.Add("Authorization", "KakaoAK 569a69082cd0c209180eb69aaaa48f1b");
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader sr = new StreamReader(stream);
            string content = sr.ReadToEnd();          

응답 결과를 Locale 개체로 만들어 Locale 컬렉션(여기서는 리스트 사용)에 보관합니다.

그리고 사용자가 선택할 수 있게 번호와 지역 명을 출력합니다.

            JavaScriptSerializer jss = new JavaScriptSerializer();
            dynamic d = jss.Deserialize<dynamic>(content);
            dynamic[] ddoc = d["documents"];
            int i = 1;
            List<Locale> locales = new List<Locale>();
            Locale locale = null;
            foreach (dynamic elem in ddoc)
            {
                string pname = elem["place_name"];
                double lng = double.Parse(elem["x"]);
                double lat = double.Parse(elem["y"]);
                locale = new Locale(pname, lng, lat);
                locales.Add(locale);
                Console.WriteLine("{0}: {1}", i, locale);
                i++;
            }

사용자로부터 번호를 입력받아 선택한 지역 개체를 반환합니다.

            Console.Write("번호를 선택:");
            int index = 0;
            int.TryParse(Console.ReadLine(), out index);
            index -= 1;
            if(index<=0 || index>locales.Count)
            {
                index = 0;
            }
            return locales[index];

SelectLocale 메서드 전체 내용은 다음과 같습니다.

        private static Locale SelectLocale(string msg)
        {
            Console.Clear();
            Console.Write(msg);
            string query = Console.ReadLine();
            string url = "https://dapi.kakao.com/v2/local/search/keyword.json";
            string query_str = string.Format("{0}?query={1}", url, query);
            WebRequest request = WebRequest.Create(query_str);
            request.Headers.Add("Authorization", "KakaoAK 569a69082cd0c209180eb69aaaa48f1b");
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader sr = new StreamReader(stream);
            string content = sr.ReadToEnd();            

            JavaScriptSerializer jss = new JavaScriptSerializer();
            dynamic d = jss.Deserialize(content);
            dynamic[] ddoc = d["documents"];
            int i = 1;
            List locales = new List();
            Locale locale = null;
            foreach (dynamic elem in ddoc)
            {
                string pname = elem["place_name"];
                double lng = double.Parse(elem["x"]);
                double lat = double.Parse(elem["y"]);
                locale = new Locale(pname, lng, lat);
                locales.Add(locale);
                Console.WriteLine("{0}: {1}", i, locale);
                i++;
            }
            Console.Write("번호를 선택:");
            int index = 0;
            int.TryParse(Console.ReadLine(), out index);
            index -= 1;
            if(index<=0 || index>locales.Count)
            {
                index = 0;
            }
            return locales[index];
        }

일단 여기에서 잘 동작하는지 확인하고 넘어가세요.

4. SK TMAP API

경로 탐색은 SK TMAP API를 사용할게요.

SK TMAP API 검색
SK TMAP API 검색

가입한 상태에서 사용할 수 있습니다. 여기에서는 가입 방법은 설명 생략할게요.

로그인 하면 다음과 같은 화면이 나옵니다.

로그인 성공 화면
로그인 성공 화면

대시보드가 자신이 개발하는 앱 등의 정보를 확인할 수 있는 곳입니다.

대시보드
대시보드

앱 만들기를 한 후에 사용할 수 있습니다. 무료 서비스로도 본 프로젝트는 문제없이 사용할 수 있습니다.

앱 만들기 방법도 설명을 생략합니다.

앱 만들기
앱 만들기

SK Open API>> TMAP 기능>>경로>>경로 안내>> 자동차 경로 안내를 선택하세요.

자동차 경로 안내
자동차 경로 안내

Url, 웹 요청에 전달할 헤더 정보, 쿼리 파라미터 및 응답 결과에 관한 메뉴얼이 나와 있습니다.

여기에서는 자세한 설명을 생략할게요.

사용할 앱 키는 대시보드>>앱>>앱키(appKey) 영역에 있습니다.

여러분의 앱키를 사용하세요. 본 프로젝트에서 사용한 앱은 이미 삭제한 상태입니다.

사용할 앱 키
사용할 앱 키

5. 경로 탐색 SearchRoute 메서드 구현

TMAP API를 사용하는 방법도 쿼리 문자열을 만드는 것 부터 시작합니다.

        private static void SearchRoute(Locale start, Locale end)
        {
            string url = "https://apis.openapi.sk.com/tmap/routes";
            string query_str = string.Format(
                "{0}?version=1&startX={1}&startY={2}&endX={3}&endY={4}&startName={5}&endName={6}",
                url, start.Lng, start.Lat, end.Lng, end.Lat, start.Pname, end.Pname);

WebRequest 개체를 생성하고 헤더 정보를 입력합니다.

appKey 부분에 앱키를 등록해야겠죠.

            WebRequest request = WebRequest.Create(query_str);
            request.Headers.Add("appKey", "Ne3DMFaXyr77xfPxjHvbd67yUjSZwaCCrv5Ogpu8");

웹 요청 개체로 웹 서버에 결과를 요청하여 WebResponse 개체로 참조합니다.

Stream 개체로 순차적으로 읽을 준비를 하고 쉽게 사용하기 위해 StreamReader 개체를 만듭니다.

그리고 ReadToEnd 메서드를 호출하여 끝까지 읽습니다.

현재 까지 읽은 내용을 출력하여 잘 동작하는지 중간 테스트를 수행하세요.

        private static void SearchRoute(Locale start, Locale end)
        {
            string url = "https://apis.openapi.sk.com/tmap/routes";
            string query_str = string.Format(
                "{0}?version=1&startX={1}&startY={2}&endX={3}&endY={4}&startName={5}&endName={6}",
                url, start.Lng, start.Lat, end.Lng, end.Lat, start.Pname, end.Pname);
            WebRequest request = WebRequest.Create(query_str);
            request.Headers.Add("appKey", "Ne3DMFaXyr77xfPxjHvbd67yUjSZwaCCrv5Ogpu8");
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader reader = new StreamReader(stream);
            string content = reader.ReadToEnd();
            Console.WriteLine(content);

중간 테스트 결과
중간 테스트 결과

Json 형식의 응답이므로 JavaScriptSerializer 개체로 역직렬화를 수행합니다.

(Console.WriteLine(content); 부분은 삭제하게요.)

            JavaScriptSerializer jss = new JavaScriptSerializer();
            dynamic dres = jss.Deserialize<dynamic>(content);
            dynamic dfea = dres["features"];

features 키에 해당 하는 항목이 경로 요소로 구성하고 있습니다.

먼저 type을 확인해 볼게요.

            dynamic dfea = dres["features"];
            foreach(dynamic d in dfea)
            {
                Console.WriteLine(d["type"]);
            }

중간 점검을 해 보면 모든 출력 내용이 feature인 것을 확인할 수 있어요.

중간 테스트
중간 테스트

geometry 키로 접근한 후에 type키로 다시 출력하는 코드로 수정하에 테스트 해 봅시다.

dynamic dfea = dres[“features”];
foreach(dynamic d in dfea)
{
    Console.WriteLine(d[“geometry”][“type”]);
}

중간 테스트
중간 테스트

Point는 지점이고 LineString은 도로입니다.

Point 혹은 LineString에 맞게 처리하는 조건문으로 코드를 수정합니다.

            dynamic dres = jss.Deserialize<dynamic>(content);
            dynamic dfea = dres["features"];
            foreach(dynamic d in dfea)
            {         
                if(d["geometry"]["type"] == "Point")
                {

                }
                if (d["geometry"]["type"] == "LineString")
                {

                    
                }
            }

Point(지점)에서 geomerty 키로 접근한 후 coordinates 키로 접근하면 위도, 경도를 확인할 수 있습니다.

그리고 properties 키로 접근한 후 description 키로 지점 이름을 알 수 있습니다.

                if(d["geometry"]["type"] == "Point")
                {
                    dynamic dd = d["geometry"]["coordinates"];
                    string pname = d["properties"]["description"];
                    Console.WriteLine("{0} ===", pname);
                    Console.WriteLine("{0}, {1}", dd[0], dd[1]);
                }

LineString(도로)에도 geomerty 키로 접근한 후 coordinates 키로 접근하면 위도, 경도를 확인할 수 있습니다. (도로는 여러 짧은 길로 구성합니다.)

그리고 properties 키로 접근한 후 name키로 도로 이름을 알 수 있습니다.

도로를 구성하는 모든 요소의 위도, 경로를 출력하는 것은 비효율적인 것 같습니다.

여기에서는 첫 번째 요소의 위도, 경도를 출력하기로 할게요.

                if (d["geometry"]["type"] == "LineString")
                {
                    dynamic dd = d["geometry"]["coordinates"];
                    string pname = d["properties"]["name"];
                    Console.WriteLine("{0} ===", pname);
                    foreach(dynamic d3 in dd)
                    {
                        Console.WriteLine("{0}, {1}", d3[0], d3[1]);
                        break;
                    }
                    
                }

SearchRoute 메서드 전체 코드는 다음과 같습니다.

        private static void SearchRoute(Locale start, Locale end)
        {
            string url = "https://apis.openapi.sk.com/tmap/routes";
            string query_str = string.Format(
                "{0}?version=1&startX={1}&startY={2}&endX={3}&endY={4}&startName={5}&endName={6}",
                url, start.Lng, start.Lat, end.Lng, end.Lat, start.Pname, end.Pname);
            WebRequest request = WebRequest.Create(query_str);
            request.Headers.Add("appKey", "Ne3DMFaXyr77xfPxjHvbd67yUjSZwaCCrv5Ogpu8");
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader reader = new StreamReader(stream);
            string content = reader.ReadToEnd();
            Console.WriteLine(content);
            JavaScriptSerializer jss = new JavaScriptSerializer();
            dynamic dres = jss.Deserialize(content);
            dynamic dfea = dres["features"];
            foreach(dynamic d in dfea)
            {                
                if(d["geometry"]["type"] == "Point")
                {
                    dynamic dd = d["geometry"]["coordinates"];
                    string pname = d["properties"]["description"];
                    Console.WriteLine("{0} ===", pname);
                    Console.WriteLine("{0}, {1}", dd[0], dd[1]);
                }
                if (d["geometry"]["type"] == "LineString")
                {
                    dynamic dd = d["geometry"]["coordinates"];
                    string pname = d["properties"]["name"];
                    Console.WriteLine("{0} ===", pname);
                    foreach(dynamic d3 in dd)
                    {
                        Console.WriteLine("{0}, {1}", d3[0], d3[1]);
                        break;
                    }
                    
                }
            }

        }

이번 프로젝트는 모두 작성하였습니다. 최종 테스트를 하면 시연 모습처럼 나올 것입니다.

6. Program.cs 소스 코드

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

namespace 경로_탐색
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Locale start = SelectLocale("출발지:");
            Locale end = SelectLocale("목적지:");
            SearchRoute(start, end);
        }

        private static void SearchRoute(Locale start, Locale end)
        {
            string url = "https://apis.openapi.sk.com/tmap/routes";
            string query_str = string.Format(
                "{0}?version=1&startX={1}&startY={2}&endX={3}&endY={4}&startName={5}&endName={6}",
                url, start.Lng, start.Lat, end.Lng, end.Lat, start.Pname, end.Pname);
            WebRequest request = WebRequest.Create(query_str);
            request.Headers.Add("appKey", "Ne3DMFaXyr77xfPxjHvbd67yUjSZwaCCrv5Ogpu8");
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader reader = new StreamReader(stream);
            string content = reader.ReadToEnd();
            Console.WriteLine(content);
            JavaScriptSerializer jss = new JavaScriptSerializer();
            dynamic dres = jss.Deserialize(content);
            dynamic dfea = dres["features"];
            foreach(dynamic d in dfea)
            {                
                if(d["geometry"]["type"] == "Point")
                {
                    dynamic dd = d["geometry"]["coordinates"];
                    string pname = d["properties"]["description"];
                    Console.WriteLine("{0} ===", pname);
                    Console.WriteLine("{0}, {1}", dd[0], dd[1]);
                }
                if (d["geometry"]["type"] == "LineString")
                {
                    dynamic dd = d["geometry"]["coordinates"];
                    string pname = d["properties"]["name"];
                    Console.WriteLine("{0} ===", pname);
                    foreach(dynamic d3 in dd)
                    {
                        Console.WriteLine("{0}, {1}", d3[0], d3[1]);
                        break;
                    }
                    
                }
            }

        }

        private static Locale SelectLocale(string msg)
        {
            Console.Clear();
            Console.Write(msg);
            string query = Console.ReadLine();
            string url = "https://dapi.kakao.com/v2/local/search/keyword.json";
            string query_str = string.Format("{0}?query={1}", url, query);
            WebRequest request = WebRequest.Create(query_str);
            request.Headers.Add("Authorization", "KakaoAK 569a69082cd0c209180eb69aaaa48f1b");
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader sr = new StreamReader(stream);
            string content = sr.ReadToEnd();            

            JavaScriptSerializer jss = new JavaScriptSerializer();
            dynamic d = jss.Deserialize(content);
            dynamic[] ddoc = d["documents"];
            int i = 1;
            List locales = new List();
            Locale locale = null;
            foreach (dynamic elem in ddoc)
            {
                string pname = elem["place_name"];
                double lng = double.Parse(elem["x"]);
                double lat = double.Parse(elem["y"]);
                locale = new Locale(pname, lng, lat);
                locales.Add(locale);
                Console.WriteLine("{0}: {1}", i, locale);
                i++;
            }
            Console.Write("번호를 선택:");
            int index = 0;
            int.TryParse(Console.ReadLine(), out index);
            index -= 1;
            if(index<=0 || index>locales.Count)
            {
                index = 0;
            }
            return locales[index];
        }
    }
}