Side Project/STUHEL (학습을 돕는 예약시스템)

초보개발자 Toy-project 개발일지 ▲NO.7 ▲ 현재위치 근처 맛집, 카카오 API 지도 검색 Javascript, KAKAO Open API, 카카오 Map

isjiji 2022. 8. 11. 10:29

 

먼저 카카오 API를 이용한 지도, 지도 검색 개발일지를 기록한  https://isjiji.tistory.com/43  게시글을 확인해주세요 ^~^

 

초보개발자 Toy-project 개발일지 ▲NO.6 ▲ 카카오 지도 API, 현재위치 근처 맛집 지도 검색 Javascript

목차  1 인사^~^ 2 오늘의 개발일지 주제 3 카카오API 사용하는방법 4 Front-end에 지도 띄우기 1. 왜 오랜만에 개발일지를 쓰나요? 개발일지를 한번 작성하면 세시간가량의 시간이 소요된다. 퇴근 후,

isjiji.tistory.com

 

 

카카오에서 제공해주는 소스를 활용하면, 지도를 띄우고 그 지도 위에 특정장소를 마킹할 수 있다.

하지만! 근처 장소 위주로 찾을 수는 없다. 

카카오 오픈소스에서 제공하는 기본 장소가 '이태원'으로 설정되어있고,

검색시에는 한국 전역을 대상으로 장소검색을 하기 때문이다. 

카카오에서 제공하는 소스를 그대로 사용하면 뜨는 지도 첫 화면

 

하지만!

아래의 사진을 보자. 중국집을 검색하면, 근처에 있는 중국집이 지도와 검색리스트에 표시되는 것을 볼 수 있다.

어떻게 하면  현재 위치를 중심으로 장소를 검색할 수 있을까? 

 

 

1.

구글로케이션을 사용하여 현재 위치를 가져온다.

navigator.geolocation.getCurrentPosition(getLocWeather, showErrorMsg); 요게 실행되면

  현재위치를 가져올 수 있는 경우  getLocWeather() 함수 실행

  현재위치를 가져올 수 없는 경우  showErrorMsg() 함수 실행

 

 

2.

getLocWeather()가 실행되면, 함수의 인자를 이용해서 위도와 경도를 가져올 수 있다. 

  var userLat = position.coords.latitude; //위도

  var userLat = position.coords.longitude; //경도

 

위도와 경도를 각각 변수에 담아준다!! 

 

 

3.

카카오에서 제공한 JSON형식의 mapOption에 위도와 경도를 담아준다. 

 

        mapOption = {
            center: new kakao.maps.LatLng(userLat, userLng), // 구글로케이션으로 받은 위도, 경도
            level: 3
        };
        map = new kakao.maps.Map(mapContainer, mapOption); //지도생성

 //참고 !! 카카오가 제공하는 기존 위치 설정임
 mapOption = {
        center: new kakao.maps.LatLng(37.566826, 126.9786567), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

 

 

4.

장소를 검색할 때, 현재위치 근처로 검색되게 하려면,  (ex '치킨' 검색시 - 사용자 근처에 있는 치킨집 검색 )

 

ps.keywordSearch(keyword, placesSearchCB, {x:userLng , y:userLat});

첫번째 인자에 검색명두번째 인자에 콜백함수세번째 인자에 경도와 위도 입력 (경도가 먼저 입력되어야함!!!!!!)

 

 

5.

현재위치를 가져올 수 없는 경우 showErrorMsg() 함수가 실행된다. 

받아온 error 인자의 code 값을 조사해서 error 이유를 alert로 띄워준다. 


function showErrorMsg(error){ 
    switch(error.code){
        case error.PERMISSION_DENIED: alert("사용자가 사용 요청을 거부했습니다."); break;
        case error.POSITION_UNAVAILABLE: alert("가져온 위치 정보를 사용할 수 없습니다."); break;
        case error.TIMEOUT: alert("요청 허용 시간을 초과했습니다."); break;
        case error.UNKNOWN_ERROR: alert("알 수 없는 오류가 발생했습니다."); break;
    }
}

 

 

 

이렇게 하면 현재위치를 가져와서 카카오지도에 세팅해줄 수 있다. 

    var userLng,userLat, mapContainer, map, infowindow,
        ps = new kakao.maps.services.Places(); // 장소 검색 객체를 생성;
    var markers = [];
	navigator.geolocation.getCurrentPosition(getLocWeather, showErrorMsg);

    function getLocWeather(position){ //현재 위치
	    userLat = position.coords.latitude; //위도
	    userLng = position.coords.longitude;//경도

        mapContainer = document.getElementById('map'), // 지도를 표시할 div
        mapOption = {
            center: new kakao.maps.LatLng(userLat, userLng), // 지도의 중심좌표
            level: 3 // 지도의 확대 레벨
        };
        map = new kakao.maps.Map(mapContainer, mapOption); //지도생성
        infowindow = new kakao.maps.InfoWindow({zIndex:1}); // 검색 결과 목록이나 마커를 클릭했을 때 장소명을 표출할 인포윈도우를 생성
        searchPlaces("맛집");
    }
    
    function searchPlaces(keyword) {// 키워드 검색 요청
        if (!keyword.replace(/^\s+|\s+$/g, '')) {
            alert('키워드를 입력해주세요');
            return false;
        }
        ps.keywordSearch(keyword, placesSearchCB,{x:userLng , y:userLat}); // 장소검색 객체를 통해 키워드로 장소검색을 요청
    }
    
    function showErrorMsg(error){ //위치찾기 오류발생
	    switch(error.code){
		    case error.PERMISSION_DENIED: alert("사용자가 사용 요청을 거부했습니다."); break;
		    case error.POSITION_UNAVAILABLE: alert("가져온 위치 정보를 사용할 수 없습니다."); break;
		    case error.TIMEOUT: alert("요청 허용 시간을 초과했습니다."); break;
		    case error.UNKNOWN_ERROR: alert("알 수 없는 오류가 발생했습니다."); break;
	    }
    }

 

 

 

 

추가로 변경한 사항들

 

 

현재위치가 결정될 때, 1)지도를 띄우고,  2)초기 키워드검색이 이뤄지도록하기 위해 아래 사항들을 추가적으로 변경했다.

 

 

1. 위도, 경도, 장소검색객체, 지도담을 div, 지도생성객체, 지도 객체, 마커/검색키워드 저장소를 

  구글로케이터 실행 이전에 선언해준다.  

      var userLng,userLat, mapContainer, map, infowindow,

      var ps = new kakao.maps.services.Places();

 

 

2.  getLocWeather() 함수에서 현재위치가 정해질 때,

  지도좌표, 지도 생성, 마커생성 로직을 입력하고, searchPlace() 함수를 실행한다.

     function getLocWeather(position){ //현재 위치
           userLat = position.coords.latitude; //위도
           userLng = position.coords.longitude;//경도


           mapContainer = document.getElementById('map'), // 지도를 표시할 div
           mapOption = {
                center: new kakao.maps.LatLng(userLat, userLng), // 지도의 중심좌표
                level: 3 // 지도의 확대 레벨
           };
           map = new kakao.maps.Map(mapContainer, mapOption); //지도생성
           infowindow = new kakao.maps.InfoWindow({zIndex:1}); // 검색 결과 목록, 마커를 클릭했을 때 장소명을 표출                     searchPlaces("맛집");
    }

 

 

3.  searchPlace() 인자에 초기 키워드를 넣는다. (사용자가 키워드 입력하기 전에 초기키워드설정해줌)

      searchPlaces("맛집");

 

 

 

html

<section>
    <div>
        <div class="row gx-4 gx-lg-5 justify-content-center">
            <div class="col-lg-8 col-xl-6 text-center">
                <h2 class="mt-0">맛집 추천</h2>
                <hr class="divider" />
            </div>
        </div></br>
        <div class="map_wrap">
            <div id="map" style="width:90%;height:100%;left:5%;bottom:3%;position:relative;overflow:hidden;"></div>
            <div id="menu_wrap" class="bg_white">
                <div class="option">
                    <div>
                        <form onsubmit="searchPlaces(this.keyword.value); return false;">
                            키워드 : <input type="text" id="keyword" size="15">
                            <button type="submit">검색하기</button>
                        </form>
                    </div>
                </div>
                <hr>
                <ul id="placesList"></ul>
                <div id="pagination"></div>
            </div>
        </div>
    </div>
</section>

 

 

javascript 

<!--지도-->
<script>
    var userLng,userLat, mapContainer, map, infowindow,
    var ps = new kakao.maps.services.Places(); // 장소 검색 객체를 생성;
    var markers = [];
   	navigator.geolocation.getCurrentPosition(getLocWeather, showErrorMsg);

    function getLocWeather(position){ //현재 위치
       userLat = position.coords.latitude; //위도
       userLng = position.coords.longitude;//경도

        mapContainer = document.getElementById('map'), // 지도를 표시할 div
        mapOption = {
            center: new kakao.maps.LatLng(userLat, userLng), // 지도의 중심좌표
            level: 3 // 지도의 확대 레벨
        };
        map = new kakao.maps.Map(mapContainer, mapOption); //지도생성
        infowindow = new kakao.maps.InfoWindow({zIndex:1}); // 검색 결과 목록이나 마커를 클릭했을 때 장소명을 표출할 인포윈도우를 생성
        searchPlaces("맛집");
    }

    function showErrorMsg(error){ //위치찾기 오류발생
       switch(error.code){
          case error.PERMISSION_DENIED: alert("사용자가 사용 요청을 거부했습니다."); break;
          case error.POSITION_UNAVAILABLE: alert("가져온 위치 정보를 사용할 수 없습니다."); break;
          case error.TIMEOUT: alert("요청 허용 시간을 초과했습니다."); break;
          case error.UNKNOWN_ERROR: alert("알 수 없는 오류가 발생했습니다."); break;
       }
    }

    function searchPlaces(keyword) {// 키워드 검색 요청
        if (!keyword.replace(/^\s+|\s+$/g, '')) {
            alert('키워드를 입력해주세요');
            return false;
        }
        ps.keywordSearch(keyword, placesSearchCB,{x:userLng , y:userLat}); // 장소검색 객체를 통해 키워드로 장소검색을 요청
    }

    function placesSearchCB(data, status, pagination) { // 장소검색이 완료됐을 때 호출되는 콜백함수
      if (status === kakao.maps.services.Status.OK) {
        displayPlaces(data);            // 정상적으로 검색이 완료됐으면 검색 목록과 마커를 표출
        displayPagination(pagination);  // 페이지 번호를 표출
        } else if (status === kakao.maps.services.Status.ZERO_RESULT) {
            alert('검색 결과가 존재하지 않습니다.');
            return;
        } else if (status === kakao.maps.services.Status.ERROR) {
            alert('검색 결과 중 오류가 발생했습니다.');
            return;
        }
    }

    // 검색 결과 목록과 마커를 표출하는 함수입니다
    function displayPlaces(places) {

        var listEl = document.getElementById('placesList'),
            menuEl = document.getElementById('menu_wrap'),
            fragment = document.createDocumentFragment(),
            bounds = new kakao.maps.LatLngBounds(),
            listStr = '';
        removeAllChildNods(listEl); // 검색 결과 목록에 추가된 항목들을 제거
        removeMarker(); // 지도에 표시되고 있는 마커를 제거

        for ( var i=0; i<places.length; i++ ) {

            var placePosition = new kakao.maps.LatLng(places[i].y, places[i].x),        // 마커를 생성하고 지도에 표시
                marker = addMarker(placePosition, i),
                itemEl = getListItem(i, places[i]); // 검색 결과 항목 Element를 생성
                bounds.extend(placePosition); // 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해 LatLngBounds 객체에 좌표를 추가

            // 마커와 검색결과 항목에 mouseover 했을때 해당 장소에 인포윈도우에 장소명을 표시합니다 mouseout 했을 때는 인포윈도우를 닫습니다
            (function(marker, title) {
                kakao.maps.event.addListener(marker, 'mouseover', function() {
                    displayInfowindow(marker, title);
                });
                kakao.maps.event.addListener(marker, 'mouseout', function() {
                  infowindow.close();
                });
                itemEl.onmouseover =  function () {
                  displayInfowindow(marker, title);
                };
                itemEl.onmouseout =  function () {
                    infowindow.close();
                };
            })(marker, places[i].place_name);

            fragment.appendChild(itemEl);
        }

        listEl.appendChild(fragment); // 검색결과 항목들을 검색결과 목록 Element에 추가
        menuEl.scrollTop = 0;

        map.setBounds(bounds);    // 검색된 장소 위치를 기준으로 지도 범위를 재설정
    }

    function getListItem(index, places) {// 검색결과 항목을 Element로 반환하는 함수

        var el = document.createElement('li'),
        itemStr = '<span class="markerbg marker_' + (index+1) + '"></span>' +
                    '<div class="info">' +
                    '   <h5>' + places.place_name + '</h5>';

        if (places.road_address_name) {
            itemStr += '    <span>' + places.road_address_name + '</span>' +
                        '   <span class="jibun gray">' +  places.address_name  + '</span>';
        } else {
            itemStr += '    <span>' +  places.address_name  + '</span>';
        }

        itemStr += '  <span class="tel">' + places.phone  + '</span>' +
                    '</div>';

        el.innerHTML = itemStr;
        el.className = 'item';

        return el;
    }


    function addMarker(position, idx, title) { // 마커를 생성하고 지도 위에 마커를 표시하는 함수
        var imageSrc = 'https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/marker_number_blue.png', // 마커 이미지 url, 스프라이트 이미지 사용
            imageSize = new kakao.maps.Size(36, 37),  // 마커 이미지의 크기
            imgOptions =  {
                spriteSize : new kakao.maps.Size(36, 691), // 스프라이트 이미지의 크기
                spriteOrigin : new kakao.maps.Point(0, (idx*46)+10), // 스프라이트 이미지 중 사용할 영역의 좌상단 좌표
                offset: new kakao.maps.Point(13, 37) // 마커 좌표에 일치시킬 이미지 내에서의 좌표
            },
            markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imgOptions),
                marker = new kakao.maps.Marker({
                position: position, // 마커의 위치
                image: markerImage
            });

        marker.setMap(map); // 지도 위에 마커를 표출
        markers.push(marker);  // 배열에 생성된 마커를 추가
        return marker;
    }

    function removeMarker() {// 지도 위에 표시되고 있는 마커를 모두 제거
        for ( var i = 0; i < markers.length; i++ ) {
            markers[i].setMap(null);
        }
        markers = [];
    }

    function displayPagination(pagination) {// 검색결과 목록 하단에 페이지번호를 표시는 함수
        var paginationEl = document.getElementById('pagination'),
            fragment = document.createDocumentFragment(),
            i;

        while (paginationEl.hasChildNodes()) {     // 기존에 추가된 페이지번호를 삭제
            paginationEl.removeChild (paginationEl.lastChild);
        }

        for (i=1; i<=pagination.last; i++) {
            var el = document.createElement('a');
            el.href = "#";
            el.innerHTML = i;

            if (i===pagination.current) {
                el.className = 'on';
            } else {
                el.onclick = (function(i) {
                    return function() {
                        pagination.gotoPage(i);
                    }
                })(i);
            }

            fragment.appendChild(el);
        }
        paginationEl.appendChild(fragment);
    }

    function displayInfowindow(marker, title) {// 검색결과 목록 또는 마커를 클릭했을 때 호출되는 함수
        var content = '<div style="padding:5px;z-index:1;">' + title + '</div>';

        infowindow.setContent(content);// 인포윈도우에 장소명을 표시
        infowindow.open(map, marker);
    }

    // 검색결과 목록의 자식 Element를 제거하는 함수
    function removeAllChildNods(el) {
        while (el.hasChildNodes()) {
            el.removeChild (el.lastChild);
        }
    }
</script>