본문 바로가기
공부/naver map api

[naver map api] 주소검색 기능 구현

by 고기 2022. 1. 27.

이전 글에서 버튼을 클릭했을 때 발생할 이벤트 함수를 작성하는 방법을 알아보았다. 이어서 naver geocoding과 naver reverse geocoding을 사용해서 주소 검색하는 방법에 대해 알아본다.

 

주소를 검색하는 방법을 두 가지 구현했다. 첫 번째는 주소 검색 텍스트박스에 주소를 입력하고 엔터를 누르거나 검색 버튼을 클릭해서 주소를 출력하는 방법이고, 두 번째는 마우스를 클릭했을 때 좌표에 해당하는 주소를 출력하는 방법이다.

 

먼저 텍스트박스에 주소를 입력해서 검색하면 searchAddress() 함수를 호출한다.

/* 검색 버튼이 눌리거나 또는 검색창에서 엔터를 누르면 searchAddress() 함수 호출 */
document.getElementById("searchBtn").onclick = function () { searchAddress();}
document.getElementById("searchAddress").onkeydown = function (e) { if (e.key === 'Enter') { searchAddress(); }}

 

searchAddress() 함수의 작동방식은 주석을 참고하면 된다. 이 과정에서 폴리곤 내부 좌표를 찾아내는 방식을 직접 구현할 뻔 했는데 다행히 라이브러리가 있었다. 65 ~ 93 line 코드는 turf.js의 turf 객체를 사용해서 폴리곤 내부 좌표를 찾아내는 코드로, 다각형 내부에 있는 점인지 아닌지 구별할 수 있다. 

/*
**********************************************************************
searchAddress()
주소를 검색하면 입력한 주소의 좌표를 획득하여 해당 좌표로 이동하면서, 
검색한 좌표에 해당하는 지번과 도로명 주소를 메세지로 출력함

작성순서
1) $(".alertAddress").detach()
   왼쪽 주소를 검색하세요 탭에서 이전에 주소를 검색했을 때 출력되었던 메세지를 지움
2) getElementById('searchAddress').value;
   왼쪽 주소를 검색하세요 탭에서 텍스트박스 내용 값을 받아옴
3) naver.maps.Service.geocode({ })
   주소에 대한 정보를 획득함 
   정보는 지번, 도로명 주소, 좌표값 등으로 response에 담김
   response에 담긴 값에 따라 왼쪽 주소를 검색하세요 탭 하단에 메세지 출력
   주소에 대한 좌표값 확인 예시 -> console.log(result, result.addresses[0].roadAddress)  
4) 주소 좌표에 해당하는 행정동 통 라인 출력
   동작방식은 clickButton()함수 참고
5) Address.push(내용) 
   Address에 지번 주소와 도로명 주소를 담음
   단, 지번 주소와 도로명 주소가 같을 경우 지번 주소만 출력함
6) infoWindow.close() & infoWindow.setContent([ ])
   이전에 출력했던 메세지를 닫고, 검색한 주소 좌표에 새로운 메세지를 출력함
   메세지 내용은 검색한 주소의 좌표 값, 지번 주소, 도로명 주소
7) marker.setPosition(위도, 경도)
   지정 좌표에 마커 출력(마커는 옵션을 주지 않았기 떄문에 마커 자체는 생성되지 않음)
8) infoWindow.open(map, marker);
   마커 위치에 메세지를 출력함
9) map.morph(위도, 경도, 줌 레벨);
   지정 좌표에 지정 레벨만큼 줌 시킴
**********************************************************************
*/        
function searchAddress() {
    $(".alertAddress").detach();
    let address = document.getElementById('searchAddress').value;

    naver.maps.Service.geocode({
        query: address
    }, function (status, response) {
        if (status === naver.maps.Service.Status.ERROR) {
            if (!address) {
                return alert('Geocode Error, Please check address');
            }
            return alert('Geocode Error, address:' + address);
        }

        if (response.v2.meta.totalCount === 0) {
            $(".option").append("<div class='alertAddress'>" +
                "[ 잘못된 주소입니다 ! ]" +
                "</div>"
            );
        }
        else {
            $(".option").append("<div class='alertAddress'>" +
                "[ 주소를 검색합니다 ! ]" +
                "</div>"
            );

            let result = response.v2,
                items = result.addresses;

            let layer = map.data.getAllFeature();
            행정동출력 =[];

            layer.forEach(function (e, index) {                
                if ((index != 0) && (index>0) && (index < 22)) {
                    if ((e['bounds'].hasPoint(new naver.maps.LatLng(result.addresses['0'].y, result.addresses['0'].x)) === true) && 행정동출력 != e['property_ADM_DR_NM']) {
                        let convertPaths = [],
                        convertInPosition = proj.fromCoordToPoint(result.addresses['0']).toArray();

                        e['geometryCollection']['0']['coords']['0']['0'].forEach(point => {
                            convertPaths.push(proj.fromCoordToPoint(point).toArray());
                        });

                        let searchWithin = turf.polygon([convertPaths]);
                        
                        if (turf.pointsWithinPolygon(turf.points([convertInPosition]), searchWithin).features.length > 0){                            
                            행정동출력.push(e['property_ADM_DR_NM']);
                            
                            deletexJson(tmp[0], tmp[1], tmp[2], tmp[3]);
                            readxJson(행정동주소[index-1], '.geojson', 행정동통개수[index-1]);
                            
                            fetch(행정동마커[index-1])
                            .then(response => response.json())
                            .then(json => map.data.addGeoJson(json))
                            .then(
                                feature =>
                                feature.forEach(function(e){
                                    map.data.overrideStyle(e, {
                                        icon: './행정동/marker/'+e['property_tong']+'.png'
                                    })
                                })
                            );
                            
                           tmp=[];
                           tmp.push(행정동주소[index-1], '.geojson', 행정동마커[index-1], 행정동통개수[index-1]);
                        }
                    }
                }
            });

            let Address = [];

            if (items[0].jibunAddress !== '') {
                Address.push('[지번 주소] ' + items[0].jibunAddress);
            }
            else {
                Address.push('[지번 주소] 가 존재하지 않음');
            }

            if ((items[0].roadAddress !== '')
                && (items[0].jibunAddress !== items[0].roadAddress)) {
                Address.push('[도로명 주소] ' + items[0].roadAddress);
            }
            else {
                Address.push('[도로명 주소] ');
            }

            법정동출력 = [];
            법정동출력 = result.addresses[0].jibunAddress.substring(result.addresses[0].jibunAddress.search("구")+1, result.addresses[0].jibunAddress.search("동")+1);
                        
            infoWindow.close();
            infoWindow.setContent([
                '<div style="padding:10px;min-width:200px;line-height:150%;">',
                '<h3 style="margin-top:5px;">검색 결과</h3>',
                '<h4>[행정동] ', 행정동출력, ', [법정동] ',법정동출력, '<br/>',
                Address.join('<br />'),'</h4>',
                '</div>'
            ].join('\n'));

            marker.setPosition(new naver.maps.LatLng(result.addresses[0].y, result.addresses[0].x));
            infoWindow.open(map, marker);
            map.morph(new naver.maps.LatLng(result.addresses[0].y, result.addresses[0].x), 16);
        }
    });
}

 

그리고 마우스 클릭을 하면 해당 좌표에 주소를 출력하는 이벤트를 구현하기 위해 마우스 리스너를 설정한다. rightclick 이벤트를 두 개 등록한 이유는 미리 naver map에 그려놓은 레이어 때문인데, 그려놓은 레이어 위에서 클릭했을 때 받아오는 object 객체인 e값에 feature 속성이 추가된다. 이 feature 속성을 이용해서 광산구 행정동을 구분하고 광산구 이외는 출력하지 않도록 하기 위함이다.

function mouseListener(){
    let tooltip = $('<div style="position:absolute;z-index:1000;padding:5px 10px;background-color:#fff;border:solid 2px #000;font-size:14px;pointer-events:none;display:none;"></div>');
    tooltip.appendTo(map.getPanes().floatPane);

    map.data.addListener('click', function (e) { ... });    
    map.data.addListener('dblclick', function (e) { ... });

    map.data.addListener('rightclick', function(e) { // 광산구 영역 주소
        console.log("mapdata", e, e.hasOwnProperty('feature'));
        searchCoordinateToAddress(e);
    });

    map.addListener('rightclick', function(e) { // 광산구 밖 영역 주소
        console.log("map", e, e.hasOwnProperty('feature'));
        searchCoordinateToAddress(e); 
    });

    map.data.addListener('mouseover', function (e) {  ... });    
    map.data.addListener('mouseout', function (e) { ... });
}

 

searchCoordinateToAddress() 함수는 마우스 오른쪽 클릭에 대한 이벤트를 구현한다. 함수의 작동방식은 주석을 참고하면 된다.

/*
**********************************************************************
searchCoordinateToAddress(e)
마우스로 지도를 클릭하면 해당 위치의 좌표를 획득하여
클릭한 좌표에 해당하는 지번과 도로명 주소를 메세지로 출력함

작성순서
1) infoWindow.close()
   이전에 출력했던 메세지를 닫음
2) naver.maps.Service.reverseGeocode({})
   좌표에 대한 정보를 획득함
   정보는 지번, 도로명 주소, 좌표값 등으로 response에 담김
3) let htmlAddresses
   지번 주소와 도로명 주소를 담을 배열
4) let tong
   통 정보를 담을 배열
5) if (e.pointerEvent['type'] == 'contextmenu') { }
   클릭한 좌표에서 통 정보를 얻어 tong에 저장
5) infoWindow.setContent([ ])
   검색한 주소 좌표에 새로운 메세지를 출력함
   메세지 내용은 검색한 주소의 좌표 값, 지번 주소, 도로명 주소
**********************************************************************
*/
function searchCoordinateToAddress(e) {
  infoWindow.close();

  naver.maps.Service.reverseGeocode({
    coords: e.coord,
    orders: [
      naver.maps.Service.OrderType.ADDR,
      naver.maps.Service.OrderType.ROAD_ADDR
    ].join(',')
  }, function(status, response) {
    if (status === naver.maps.Service.Status.ERROR) {
      if (!e.coord) {
        return alert('ReverseGeocode Error, Please check latlng');
      }
      if (e.coord.toString) {
        return alert('ReverseGeocode Error, latlng:' + e.coord.toString());
      }
      if (e.coord.x && e.coord.y) {
        return alert('ReverseGeocode Error, x:' + e.coord.x + ', y:' + e.coord.y);
      }
      return alert('ReverseGeocode Error, Please check latlng');
    }
    
    let address = response.v2.address,
        htmlAddresses = [];
    
    if (address.jibunAddress !== '') {
        htmlAddresses.push('[지번 주소] ' + address.jibunAddress);
    }
    else{
        htmlAddresses.push('[지번 주소] 가 존재하지 않음');
    }
    
    if (address.roadAddress !== ''){
        htmlAddresses.push('[도로명 주소] ' + address.roadAddress);
    }
    else {
        htmlAddresses.push('[도로명 주소] ');
    }

    let tong = [];

    행정동출력 = [];
    if (Object.keys(e).length===7){
        행정동출력 = e['feature']['property_ADM_DR_NM']
      }
      else{
        행정동출력 = '광산구 영역이 아닙니다'
      }
    
    법정동출력 = [];
    법정동출력 = htmlAddresses[0].substring(htmlAddresses[0].search("구")+1, htmlAddresses[0].search("동")+1);
    
    if (e.pointerEvent['type'] == 'contextmenu') {
        if (e.feature['property_tong'] != undefined) {
            tong.push('[통 정보] ' + e.feature['property_tong'] + '통');
        }
        else {
            tong.push('[통 정보] 통 정보가 없습니다.');
        }
    }

    infoWindow.setContent([
      '<div style="padding:10px;min-width:200px;line-height:150%;">',
      '<h3 style="margin-top:5px;">검색 결과</h3>',
      '<h4>[행정동] ', 행정동출력, ', [법정동] ', 법정동출력, '<br/>',
      htmlAddresses[0], '<br/>', htmlAddresses[1],'</h4>',
      '</div>'
    ].join('\n'));

    infoWindow.open(map, e.coord);  
  });  
}

 

주소 검색 기능을 웹페이지에서 실행시켜보면 다음과 같다.

사진1

 

여기까지 주소 검색 이벤트 작성에 대해 알아보았다. 다음 글에서는 지금까지 작성한 아키텍처에 대한 설명과 코드 전반을 작성하는 것으로 글을 마치려고 한다.

댓글