'카카오지도 활용 구현'에 해당되는 글 1건

728x90

"카카오 맵 키워드로 장소검색하고 목록으로 표출하기" 로 검색된 결과 URL 이다.

https://apis.map.kakao.com/web/sample/keywordList/

위 UTL 예시에 나온 코드로는 부족하므로 클러스터링 등 내용을 더 찾아서 살을 붙여야 한다.

 

화면 구성을 위해서 아래와 같이 HTML 을 변경한다.

아래 HTML 코드에서 카카오맵 API Key는 삭제했다.

<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>카카오지도</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/mdb.lite.css">
<link rel="stylesheet" href="/css/jquery-ui.css">
<link rel="stylesheet" href="/css/kakaomap.css"/>
</head>
<body>
<div class="card">
<div class="my-0 mx-2">
    <div class="form-check form-check-inline">
        <button type="button" class="btn btn-outline-primary btn-sm px-2" id="searchToggle">OFF</button>
    </div>
 
    <div class="form-check form-check-inline">
        <p class="fs-4">반경</p>
    </div>
    <div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="radius" id="dis1" value="0.5" checked />
      <label class="form-check-label" for="dis1">0.5</label>
    </div>
 
    <div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="radius" id="dis2" value="1.0" />
      <label class="form-check-label" for="dis2">1</label>
    </div>
 
    <div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="radius" id="dis3" value="1.5" />
      <label class="form-check-label" for="dis3">1.5</label>
    </div>
    <div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="radius" id="dis4" value="2.0" />
      <label class="form-check-label" for="dis4">2</label>
    </div>
 
    <div class="form-check form-check-inline">
        <button type="button" class="btn btn-outline-primary btn-sm px-2" id="curLoc">현위치</button>
    </div>
 
</div>
<div class="mt-1 mb-2 mx-2">
    <div class="map_wrap">
        <div id="map" style="width:100%;height:800px;position:relative;overflow:hidden;"></div>
 
        <div id="menu_wrap" class="bg_white">
            <div class="option">
                <div class="form-check form-check-inline">
                    <form onsubmit="searchPlaces(); return false;">
                        <input type="text" value="" id="keyword" size="15"> 
                        <button type="submit">&nbsp;검색</button>
                    </form>
                    <img src="./marker/icon_eraser.png" alt="clear" height="30" width="30" style="margin-left: 10px;" id="eraser">
                </div>
            </div>
            <div id="datacount"></div>
            <hr>
            <ul id="placesList"></ul>
            <div id="pagination"></div>
        </div>
        <div id="dialog" title="지도 Infowindow"></div>
    </div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script type="text/javascript" src="/js/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/mdb.lite.min.js"></script>
<script type="text/javascript" src="/js/jquery-ui.js"></script>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=&libraries=services,clusterer,drawing"></script>
<script type="text/javascript" src="Map.js"></script>
<script>
$("#searchToggle").click(function(){
    // 지도 검색창 ON/OFF
    if($("#menu_wrap").css("display")=="none"){
        $("#menu_wrap").show();
        $("#searchToggle").text('OFF');
    } else {
        $("#menu_wrap").hide();
        $("#searchToggle").text('ON');
    }
});
 
$("input[name='radius']").change(function(){
    searchPlaces();
});
$("#curLoc").click(function(){
    // 현재 지도의 중심좌표 구하기
    searchPlaces();
});
 
$("#eraser").click(function(){
    $("#keyword").val('');
});
</script>
</body>
</html>

 

Map.js

변수는 let 과 const 로 전부 변경하고, Ajax 로 PHP와 연동된 DB 자료를 가져다가 출력하는 걸 만든다.

    // 마커를 담을 배열입니다
    let markers = [];
 
    const mapContainer = document.getElementById('map'), // 지도를 표시할 div
    mapOption = {
        center: new kakao.maps.LatLng(37.566826126.9786567), // 지도의 중심좌표
        level: 12 // 지도의 확대 레벨
    };
 
    // 지도를 생성합니다
    const map = new kakao.maps.Map(mapContainer, mapOption);
 
    // 마커 클러스터러를 생성합니다
    const clusterer = new kakao.maps.MarkerClusterer({
        map: map, // 마커들을 클러스터로 관리하고 표시할 지도 객체
        averageCenter: true// 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
        minLevel: 6 // 클러스터 할 최소 지도 레벨
    });
 
    // 일반 지도와 스카이뷰로 지도 타입을 전환할 수 있는 지도타입 컨트롤을 생성합니다
    const mapTypeControl = new kakao.maps.MapTypeControl();
 
    // 지도에 컨트롤을 추가해야 지도위에 표시됩니다
    // kakao.maps.ControlPosition은 컨트롤이 표시될 위치를 정의하는데 TOPRIGHT는 오른쪽 위를 의미합니다
    map.addControl(mapTypeControl, kakao.maps.ControlPosition.TOPRIGHT);
 
    // 지도 확대 축소를 제어할 수 있는  줌 컨트롤을 생성합니다
    const zoomControl = new kakao.maps.ZoomControl();
    map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHTBOTTOM);
 
    // 지도가 이동, 확대, 축소로 인해 중심좌표가 변경되면 마지막 파라미터로 넘어온 함수를 호출하도록 이벤트를 등록합니다
    let getLat = null;
    let getLng = null;
    kakao.maps.event.addListener(map, 'center_changed'function() {
        // 지도의  레벨을 얻어옵니다
        const level = map.getLevel();
        // 지도의 중심좌표를 얻어옵니다
        const latlng = map.getCenter();
        console.log('중심 위도: '+latlng.getLat() + ', 경도: ' + latlng.getLng());
        getLat = latlng.getLat();
        getLng = latlng.getLng();
    });
 
    // 장소 검색 객체를 생성합니다
    const ps = new kakao.maps.services.Places();
 
    // 검색 결과 목록이나 마커를 클릭했을 때 장소명을 표출할 인포윈도우를 생성합니다
    const infowindow = new kakao.maps.InfoWindow({zIndex:1});
 
    // 키워드로 장소를 검색합니다
    searchPlaces();
 
    // 키워드 검색을 요청하는 함수입니다
    function searchPlaces() {
        let keyword = document.getElementById('keyword').value;
        let radius = $("input[name='radius']:checked").val();
 
        $.ajax({
            url : 'ajaxMap.php',
            type : 'GET',
            data : { 'msg': keyword, 'radius':radius, 'lat':getLat, 'lng':getLng },
            dataType: 'json',
            success : function(json) {
                if(json.length == 0) {
                    alert('검색 데이터가 없습니다.');
                    return;
                }
 
                console.log('데이터 갯수 :: ' + json.length);
                displayPlaces(json);
 
                // 클러스터에 마커들을 추가합니다
                clusterer.addMarkers(markers);
 
                var pagination = [ '1''2''3' ];
                displayPagination(pagination);
            },
            error : function(xhr, status, error){
                alert("에러코드 : " + xhr.status); //요청에 실패하면 에러코드 출력
            },
            complete : function() {
                
            }
        });
    }
 
    // 장소검색이 완료됐을 때 호출되는 콜백함수 입니다
    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) {
        let listEl = document.getElementById('placesList'),
        menuEl = document.getElementById('menu_wrap'),
        fragment = document.createDocumentFragment(),
        bounds = new kakao.maps.LatLngBounds(),
        listStr = '';
 
        // 검색 결과 목록에 추가된 항목들을 제거합니다
        removeAllChildNods(listEl);
 
        // 지도에 표시되고 있는 마커를 제거합니다
        removeMarker();
 
        const cntEl = document.getElementById('datacount');
        cntEl.innerText = '검색 개수 : ' + places.length;
 
 
        for (let i=0; i<places.length; i++) {
 
            let color_marker = "marker-icon-blue.png";
 
            if (places[i].yn == 0)  { // 없으면 => 빨간색
                color_marker = "marker-icon-red.png";
                if(places[i].isbiz > 0) {
                    color_marker = "marker-icon-green.png";
                }
            } else {
                color_marker = "marker-icon-blue.png";
                if(places[i].isbiz > 0) {
                    color_marker = "marker-icon-orange.png";
                }
            }
 
            // 마커를 생성하고 지도에 표시합니다.
            let idx = places[i].idx; // 절대 var 변수를 선언하면 안된다. 반드시 let 변수를 선언해야 제대로 동작되더라. 개삽질을 엄청했다.
            let title = places[i].idx;
            let placePosition = new kakao.maps.LatLng(places[i].latitude, places[i].longitude);
            let marker = addMarkers(placePosition, color_marker, title);
            let itemEl = getListItem(i, places[i], color_marker); // 검색 결과 항목 Element를 생성합니다
 
            // 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해 LatLngBounds 객체에 좌표를 추가합니다
            bounds.extend(placePosition);
 
            // 마커와 검색결과
            // 항목에 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();
                };
 
                // 마커에 클릭 이벤트를 등록한다.
                kakao.maps.event.addListener(marker, 'click'function() {
                    //map.panTo(placePosition); // 지도 중심을 부드럽게 이동시킨다.
                    console.log('idx:' + idx);
                    custom_infowindow(idx);
 
                });
 
            })(marker, places[i].bldNM);
 
            fragment.appendChild(itemEl);
        }
 
        // 검색결과 항목들을 검색결과 목록 Elemnet에 추가합니다
        listEl.appendChild(fragment);
        menuEl.scrollTop = 0;
 
        // 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다
        map.setBounds(bounds);
    }
 
    function custom_infowindow(idx){
        $('#dialog').load('infowindow.php?do='+idx, function() {
            $(this).dialog({
                autoOpen : true,
                height : 'auto',
                width : 'auto',
                position: { my: "center", at: "center", of: window },
                title : 'XX정보',
                buttons : {
                    "닫기" : function() {
                        $(this).dialog("close");
                    }
                }
            });
        });
 
    }
 
    // 검색결과 항목을 Element로 반환하는 함수입니다. 위도, 경도, 번호, 건물명, 도로명주소, 지번주소
    function getListItem(index, places, _color) {
        let el = document.createElement('li'),
        itemStr = '<span style="float:left;position:absolute;width:33px; height:31px;margin:10px 0 0 10px;"><img width="31" height="31" src="./marker/' + _color + '"></span>' +
                  '<div class="info">' +
                  '   <h5>' + places.bldNM + '</h5>';
 
        if (places.stAddress) {
            itemStr += '   <span>' + places.stAddress + '</span>' +
                       '   <span class="jibun gray">' +  places.jiAddress  + '</span>';
        } else {
            itemStr += '   <span>' +  places.jiAddress  + '</span>';
        }
 
        itemStr     += '  <span class="tel">' + places.idx  + '</span>' +
                       '</div>';
 
        el.innerHTML = itemStr;
        el.className = 'item';
 
        return el;
    }
 
 
    function addMarkers(position, _color, title) {
        const imageSrc = './marker/' + _color;
        const imageSize = new kakao.maps.Size(3333);
        const imageOptions = {
            offset: new kakao.maps.Point(2769)
        };
 
        const markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOptions)
        // 마커를 생성합니다
        const marker = new kakao.maps.Marker({
            position: position,
            //title: title,
            image: markerImage
        });
 
 
        marker.setMap(map); // 마커가 지도 위에 표시되도록 설정합니다
        markers.push(marker); // 생성된 마커를 배열에 추가합니다
 
        return marker;
    }
 
    // 지도 위에 표시되고 있는 마커를 모두 제거합니다
    function removeMarker() {
        //clusterer.removeMarkers(markers);
        clusterer.clear(); // 추가된 모든 마커 삭제
        for ( let i = 0; i < markers.length; i++ ) {
            markers[i].setMap(null);
        }
        markers = [];
    }
 
    // 검색결과 목록 하단에 페이지번호를 표시는 함수입니다
    function displayPagination(pagination) {
        let paginationEl = document.getElementById('pagination'),
            fragment = document.createDocumentFragment(),
            i;
 
        // 기존에 추가된 페이지번호를 삭제합니다
        while (paginationEl.hasChildNodes()) {
            paginationEl.removeChild (paginationEl.lastChild);
        }
 
        for (i=1; i<=pagination.last; i++) {
            let 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) {
        const 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);
        }
    }
 
 

 

백엔드 PHP 코드는 여기에는 적지 않는다.

화면에 뿌리는 marker 의 개수와 성능은 백엔드 Query를 어떻게 구현하느냐에 따라 달라진다.

가능하면 SELECT 문의 결과를 최소로 하여 원하는 결과를 찾을 수 있게 구현한다.

 

 

 

 

 

 

'React > morden javascript' 카테고리의 다른 글

Javascript Array.filter 사용 예제  (0) 2022.12.27
Javascript Array.map 사용 예제  (2) 2022.12.27
[Javascript] 정규표현식  (0) 2022.10.08
[Javascript] _cloneDeep()  (0) 2022.10.05
[ES6] Spread 연산자  (0) 2022.10.02
블로그 이미지

Link2Me

,