"코딩세끼"

Kakao Map API 사용하기 본문

프로그래머로 전직하기/첫번째 프로젝트 : Federico

Kakao Map API 사용하기

StrrayNight 2022. 2. 12. 21:42
반응형

1. 요구사항

- 매장찾기 지역명 : 지역별로 위치한 매장 찾기.

- 매장찾기 매장명 :  매장 주소를 검색해서 키워드로 찾기. 

- 매장찾기 현위치 :  내 위치에서 5Km이내에 있는 매장을 찾기.

 

벤치마킹 사이트 : 피자알볼로

 

2. 구현

- 사용 API : Kakao API

- DB 환경 : 

             1. 주소찾기API를 통해 입력된 시/도, 구/군, 상세주소를 별도컬럼에 저장한다.

             2. API 입력받은 주소를 기준하여 위도(xLatitude), 경도(yLongtitude)를 저장한다. 

2.1 지역명 찾기

- 검색의 폭을 줄이는 방식으로 접근함.

Depth1(Sido) 과 Depth2(Gugun) 을 기준으로 나누어 Depth1의 기준으로 Depth2를 찾고 없으면 alert!

Controller 에서는 List Type으로 자료를 담아와서 전달하였다. 

 

- Sql 문

<select id="selectFcAddress" resultType="vo.FranchiseVO">
  select FCID, FCAREA, FCADDRESS, FCPHONE,lat,lon from franchise 
  where fcarea = #{Depth1} and 
  fcaddress like ('%'||#{Depth2}||'%')
  order by fcid
</select>

<!-- 지역명선택시 -->
	<div class="row " id="outer_1" style=" margin-top:50px;">
	    <!-- 지역명 시도, -->	   
		<div class="col" style="height: 64px;">
			<select class="form-select form-select-lg" id="Sido" name="Sido" 
			 aria-label="Default select example" onchange="depth1_change(value); $('#fcInfo_all').empty();" >
		   	  <option value='none'>시/도 선택</option>
              <option value="강원">강원</option>
              <option value="경기">경기</option>
              <option value="경남">경남</option>
              <option value="경북">경북</option>
              <option value="광주">광주</option>
              <option value="대구">대구</option>
              <option value="대전">대전</option>
              <option value="부산">부산</option>
              <option value="서울">서울</option>
              <option value="세종">세종</option>
              <option value="울산">울산</option>
              <option value="인천">인천</option>
              <option value="전남">전남</option>
              <option value="전북">전북</option>
              <option value="제주">제주</option>
              <option value="충남">충남</option>
              <option value="충북">충북</option>
			</select>
			<div id ="result"></div>
		</div>
		<!-- 지역명 구군 -->
		<div class="col" style="height: 64px;">
			<select class="form-select form-select-lg" id="Gugun" name="Gugun" aria-label="Default select example" onchange="depth2_change(value); $('#fcInfo_all').empty();">
					
				<option value="none">지역을 선택하세요.</option>
			</select>			
		</div>	
	</div>
	<!-- 지역명선택 종료 -->
		
		<!-- 지도표시시작 -->
	<div class="row" id="outer_1_map" style="display:block; margin-top:40px;" >
		<div class="col" style="height: 600px; margin-bottom:50px;overflow: hidden;">
		<!-- 지도본체시작 -->
			<div id="map" style="width:100%; height:100%; border: 1px solid #DC3545;"></div>	
		<!-- 지도본체종료 -->
		</div>
		<script src="//dapi.kakao.com/v2/maps/sdk.js?appkey=63fe094a0bad5ef07be77c4f00959da2&libraries=services"></script>
		
				
		<script>
		let mapContainer = document.getElementById('map'), // 지도를 표시할 div 
		   mapOption = {
		       center: new kakao.maps.LatLng(37.359515565737276, 127.10538427434284), // 지도의 중심좌표
		       level: 3 // 지도의 확대 레벨		
		};
		    // 지도를 생성합니다    
   		let map = new kakao.maps.Map(mapContainer, mapOption); 
// Depth1 이 바뀌고 depth2에 defualt값으로 들어가는 것도 실행 돼야 하기 때문에
// depth2에 대한 event도 함께 포함한다.
function depth1_change(e){   	
   	// 서울   	
	const SU = ['강남구','강동구','강북구','강서구','관악구','광진구','구로구',
			  '금천구','노원구','도봉구','동대문구','동작구','마포구','서대문구',
			  '서초구','성동구','성북구','송파구','양천구','영등포구','용산구',
			  '은평구','종로구','중구','중랑구'];
	//경기
	const GG = ['고양시 덕양구','고양시 일산구','과천시','광명시','광주시','구리시','군포시',
			'김포시','남양주시','동두천시','부천시 소사구','부천시 오정구','부천시 원미구','성남시 분당구',
			'성남시 수정구','성남시 중원구','수원시 권선구','수원시 장안구',
			'수원시 팔달구','시흥시','안산시 단원구','안산시 상록구','안성시','안양시 동안구',
			'안양시 만안구','오산시','용인시','의왕시','의정부시','이천시','파주시','평택시',
			'하남시','화성시','가평군','양주군','양평군','여주군','연천군','포천군'];
	//인천
	const IC = [ "계양구", "미추홀구", "남동구", "동구", "부평구", "서구", "연수구", "중구", "강화군", "옹진군" ];
	//강원
	const GW =  [ "춘천시", "원주시", "강릉시", "동해시", "태백시", "속초시", "삼척시", "홍천군", "횡성군", "영월군", "평창군", "정선군", "철원군", "화천군", "양구군", "인제군", "고성군", "양양군" ];
	//충남
	const CN =  [ "천안시 동남구", "천안시 서북구", "공주시", "보령시", "아산시", "서산시", "논산시", "계룡시", "당진시", "금산군", "부여군", "서천군", "청양군", "홍성군", "예산군", "태안군" ];
	//충북
	const CB =  [ "청주시 상당구", "청주시 서원구", "청주시 흥덕구", "청주시 청원구", "충주시", "제천시", "보은군", "옥천군", "영동군", "증평군", "진천군", "괴산군", "음성군", "단양군" ];
	//대전
	const DJ = [ "대덕구", "동구", "서구", "유성구", "중구" ] ;
	//세종
	const SJ =  [ "세종특별자치시" ];
	//광주
	const GJ =  [ "광산구", "남구", "동구", "북구", "서구" ];
	//전북
	const JB =  [ "전주시 완산구", "전주시 덕진구", "군산시", "익산시", "정읍시", "남원시", "김제시", "완주군", "진안군", "무주군", "장수군", "임실군", "순창군", "고창군", "부안군" ];
	//전남
	const JN =  [ "목포시", "여수시", "순천시", "나주시", "광양시", "담양군", "곡성군", "구례군", "고흥군", "보성군", "화순군", "장흥군", "강진군", "해남군", "영암군", "무안군", "함평군", "영광군", "장성군", "완도군", "진도군", "신안군" ];
	//경북
	const GB =  [ "포항시 남구", "포항시 북구", "경주시", "김천시", "안동시", "구미시", "영주시", "영천시", "상주시", "문경시", "경산시", "군위군", "의성군", "청송군", "영양군", "영덕군", "청도군", "고령군", "성주군", "칠곡군", "예천군", "봉화군", "울진군", "울릉군" ];
	//경남
	const GN =  [ "창원시 의창구", "창원시 성산구", "창원시 마산합포구", "창원시 마산회원구", "창원시 진해구", "진주시", "통영시", "사천시", "김해시", "밀양시", "거제시", "양산시", "의령군", "함안군", "창녕군", "고성군", "남해군", "하동군", "산청군", "함양군", "거창군", "합천군" ];
	//부산
	const BS =  [ "강서구", "금정구", "남구", "동구", "동래구", "부산진구", "북구", "사상구", "사하구", "서구", "수영구", "연제구", "영도구", "중구", "해운대구", "기장군" ];
	//대구
	const DG =  [ "남구", "달서구", "동구", "북구", "서구", "수성구", "중구", "달성군" ];
	//울산
	const US =  [ "남구", "동구", "북구", "중구", "울주군" ];
	//제주
	const JJ =  [ "서귀포시", "제주시" ];
	console.log("ID=Sido => "+$('#Sido').val());
	var target = document.getElementById("Gugun");
	
	target.options.length = 0;
	
	if(e == "경기") var Depth2 = GG;						
   	else if(e == "서울")var Depth2 = SU;
   	else if(e == "경남")var Depth2 = GN;
   	else if(e == "경북")var Depth2 = GB;
   	else if(e == "광주")var Depth2 = GJ;
   	else if(e == "대구")var Depth2 = DG;
   	else if(e == "대전")var Depth2 = DJ;
   	else if(e == "부산")var Depth2 = BS;
   	else if(e == "울산")var Depth2 = US;
   	else if(e == "인천")var Depth2 = IC;
   	else if(e == "전남")var Depth2 = JN;
   	else if(e == "전북")var Depth2 = JB;
   	else if(e == "제주")var Depth2 = JJ;
   	else if(e == "충남")var Depth2 = CN;
   	else if(e == "충북")var Depth2 = CB;
   	else if(e == "강원")var Depth2 = GW;
	else if(e == "세종")var Depth2 = SJ;
	
	for (x in Depth2) {
		var option = document.createElement("option");
		option.value = Depth2[x];
		option.innerHTML = Depth2[x];
		target.appendChild(option);
	} // for
	depth2_change($('#Gugun').val());
	
}// function
//function depth2
function depth2_change(e){
	
	console.log("ID=Gugun => "+$('#Gugun').val());
	
	var Depth2 = e;
	var Depth1 = $('#Sido').val();
	
	$.ajax ({		
		type : 'get',
		url : 'fcSearchArea',
		headers : {Authorization:'KakaoAK {63fe094a0bad5ef07be77c4f00959da2}'},
		data : {
			Depth1 : Depth1 ,
			Depth2 : Depth2		
		},		
		
		success : function(data){
			
			if(data.success=='success'){				
				console.log("data.list : "+ data.list+" <=controller");
				$('#fcInfo_all').empty();
				
				if(data.list.length<1){
					alert ("가맹점이 없습니다. \n다시 검색해주세요.")
					return;
				}	
				setMarkers();
				var addrs = [];
				var contents = [];
				var fcPhone = [];
							
				var list = data.list;
				
				for(var i = 0 ; i<list.length;i++){
					
					var fc = {};
					fc.fcAddress=list[i].fcAddress;
					fc.fcId=list[i].fcId;
					fc.fcPhone=list[i].fcPhone;
					fc.ylat=list[i].lat;
					fc.xlon=list[i].lon;
					
					addrs.push(fc);
					console.log("addrs.length = ",addrs.length);
					
					}// for
				
				// 지도의 중심좌표를 표시 위치에 따라 재설정 하기위한 bounds 생성	
				var bounds = new kakao.maps.LatLngBounds();	
				var total = (addrs.length);
				console.log("total",total);
				var counter = 0;
					
				markingMap();
				
				
// ========================================== 프랜차이즈 마킹 시작 !! =================================================
										
										function markingMap() {
											
											var contents = [];									
	
											for(var i=0; i<total; i++) {
											var coords = new kakao.maps.LatLng(addrs[i].ylat, addrs[i].xlon);
											console.log('i='+i+'yLat[i]='+addrs[i].ylat+' xLng[i]='+addrs[i].xlon+' fcId =',addrs[i].fcId);
												// marking 좌표를 포함하도록 영역 정보를 확장한다.
												bounds.extend(coords);
												
											
												// 마커를 생성합니다
											    	marker = new kakao.maps.Marker({
											        map: map, // 마커를 표시할 지도
											        position: coords,// 마커의 위치
											        image : icon
											    }); 
													markers.push(marker); //지역명
											
													// 커스텀 오버레이를 생성합니다
													var content = '<div class="customoverlay" >' + 
								                    '<div class="title">' + addrs[i].fcId+ '점</div>' +
								                    '</div>';
													
													var customOverlay = new kakao.maps.CustomOverlay({
													    position: coords,
													    content: content,
													    yAnchor: 3
													});
													
												    (function(marker, customOverlay) {
												        // 마커에 mouseover 이벤트를 등록하고 마우스 오버 시 인포윈도우를 표시합니다 
												        kakao.maps.event.addListener(marker, 'mouseover', function(mouseEvent) {
												        	customOverlay.setMap(map);
												        });
												        // 마커에 mouseout 이벤트를 등록하고 마우스 아웃 시 인포윈도우를 닫습니다
												        kakao.maps.event.addListener(marker, 'mouseout', function(mouseEvent) {
												        	customOverlay.setMap(null);
												        });
												    })(marker, customOverlay);
// ========================================== 프랜차이즈 마킹 종료 !! =================================================				
											} //for
													
											
											    map.setBounds(bounds);	
											} // function markingMap
										 // 마커에 이벤트를 등록하는 함수 만들고 즉시 호출하여 클로저를 만듭니다
									    // 클로저를 만들어 주지 않으면 마지막 마커에만 이벤트가 등록됩니다
									   
					
// 				card 밑에 띄우기
					
					$('#fcInfo').load('fcSearch?card=card #fcInfo'); // 지역명 카드띄우기 시작.	
					
				} //if(data.success=='success')
					
				else {
					
					alert('해당 지역에 가맹점이 없습니다.');
					
					}			
						}, error : function(){
							alert("통신 오류입니다. 다시 시도하세요.");
						}
					})//ajax
				}// function
		</script>	
	</div>
	<!-- 지도표시종료 -->

★ 작성시 시행착오

1. 주소를 좌표로 변환하는데에 필요한 설계 오류.

2. 주소를 가맹점 Create 시 바로 y경도 x위도로 변경해야 하는데 중점을 둬야 한다는 것에는 중요치 않게 여겼다. 

 

● 그런데 왜 중요해졌냐?

 

- js 작성시 ajax 를 사용하는 방식을 주로 사용했고, ajax 사용시 "A= 콜백Data(주소)를 불러오는 시간" + 

"B= 주소를 좌표로 변환해주는 시간" 이 "C=지역이름으로 요청되는 그 순간!!" 에 Data가 정확하고 빠르게 응답이 와야 하는데, 그렇지 못했고 Data도 마지막 Data 혹은 꼬여진 Data로 표현되는 오류가 있었다. 

 

요청, 응답(C ,  A+B) 시간이 같거나 (A+B)가 커버리니까 DATA의 오류가 발생했었다, 하지만 이를 해결하는 방법이 콜백지옥이라는 지옥의 콜백함수인 promise(), then() 이고 이를 작성하여 해결하는 것보다, DB에 저장할 때 컬럼 두개를 만들어서 주소입력시 바로 주소를 좌표로 변환하는 형태가 만들어지는 것이 더 좋은 DATA 관리 및 효율이 좋게 느껴졌다. 

 

해당 주소 -> 좌표 변경 코드. 

$('#fcSubmitBtn').click(function() {
		if (fcincheck() == true) {
			var geocoder = new kakao.maps.services.Geocoder(); // jsgeocode
			var address = $('#address').val();
			var lat;
			var lon;
			geocoder.addressSearch(address, function(result, status) { // jsgeocode
		    if (status === kakao.maps.services.Status.OK) {
				console.log('result', result);
				lat = result[0].y; // 경도
				lon = result[0].x; // 위도
		 		console.log('lat', lat);
		 		console.log('fcPassword', $('#fcPassword').val());
				$.ajax({
					type : "post",
					url : "fcinsert",	
					data : {
						fcId : $('#fcId').val(),
						fcPassword : $('#fcPassword').val(),
						fcName : $('#fcName').val(),
						fcAddress : $('#address').val()+" "+$('#addressDetail').val(),
						fcArea : $('#fcArea').val(),
						fcPhone : $('#fcPhone').val(),
						hoId : $('#fchoId').val(),
						fcClose : "N",
						lat : lat, // 경도
						lon :lon  // 위도
					},
					success : function(data) {
						if (data.success == 'success') {
							alert('계정생성에 성공했습니다.');
							fcInputClear();
							location.reload();
						}
						if (data.success == 'fail') {
							alert('계정생성에 실패했습니다.');
							fcInputClear();
						}
					},
					error : function() {
						alert("서버와 접속에 실패했습니다.");
						fcInputClear();
					}
				})// ajax
				
	
	 		 }// if geocoder 성공
				else{
					alert("서버와 접속이 실패했습니다.");
					fcInputClear();
				}
			}) //geocoder
		}// if check
	})// $('#fcSubmitBtn').click(function()

 

3. 어려웠지만, 일단 구현한 코드 

- 작성하면서 특이하게 느꼈던 점. 

HTML에서 <script></script> tag를 써서 작성하는데, 여기까지는 뭐 크게 이해에 어렵진 않았다.

그러나 내가 어려웠던 부분은 안에서 function 0000 ()을 순서대로 잘 불러오지 못했다는 점, 

이점이 좀 생각보다 어려원 점이었다. 이부분을 study 해보니 passing 이라는 개념이 있던데, 

하면서 공부할 개념은 아니었던 것 같다. passing 과 callback과 promise() then()으로 해결한다는

것을 알았다. 

 

개인적으로 반복을 왠만하면 줄이고자 노력하고 있는데, 이번에는 많은 시간을 쏟아 부어도 안됐을만큼 

HTML <body>태그에 기재하는 <script>문 어디서 로딩순서가 꼬였는지 모를정도로 실행 순서에 이해가 어려웠다.

 

4. 구현된 실제화면

 

 

 

반응형

'프로그래머로 전직하기 > 첫번째 프로젝트 : Federico' 카테고리의 다른 글

JNI 오류( A jni error)  (0) 2022.01.31
2. 메뉴 삭제 관리  (0) 2022.01.28
1. 본사-메뉴-삭제  (0) 2022.01.22
오늘 공부한 내용.  (0) 2022.01.14