2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

KaKaoMap 내 위치 기반 db에 등록한 업체 확인하기 - Uncaught (in promise) TypeError: Cannot read properties of undefined 해결

2023. 9. 15. 15:31ㆍ트러블 슈팅
반응형

 

구현 기능

  •  kakaomap 에서 지원해 주는 Maps API의 Web API를 이용
  • 나의 위치 정보를 가져와 지도 중심좌표를 생성
  • db에 저장되어 있는 업체들의 주소를 불러와 맵에 마커를 표시한다.

kakaoMap Maps API 이용 방법

  • kakao developers에 접속하여 애플리케이션을 추가, 플랫폼 web을 선택하여 사용한다.
  • web에 사용할 것이기 때문에 발급된 JavaScript Key를 복사하여 카카오 지도를 나타내고자 하는 HTML에 추가한다.
<script
      type="text/javascript"
      src="//dapi.kakao.com/v2/maps/sdk.js?appkey=yourkey&libraries=services,clusterer,drawing">
</script>

 

위치 정보 가져오기

  • 카카오 맵에서 제공하는 geolocation 기능을 이용하면 위치 정보를 받아 올 수 있다.
function currentLocation() {
    if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(function (position) {
              // Geolocation 접속 위치 불러오기

              let lat = position.coords.latitude; //위도
              let lon = position.coords.longitude; //경도

              let mapContainer = document.getElementById('map'), // 지도를 표시할 div
                mapOption = {
                  center: new kakao.maps.LatLng(lat, lon), // 지도의 중심좌표
                  level: 5, // 지도의 확대 레벨
                };

              const map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다
              ...
  • 함수 currentLocation()을 생성 후 geolocation을 이용하여 현재 위치를 받아온다.
  • 위치정보 권한을 수락하면 내 위치를 중심으로 한 지도가 생성된다.

db에 저장된 comapny 정보 가져온 후 지도에 마커 찍어주기

axios.get('/api/company').then(function (response) {
    const result = response.data;
    
    function currentLocation() { ...
  • axios를 이용하여 db에 저장되어 있는 정보를 불러와 result 변수에 저장하였다.

let positions = [{ content: `<div">내 위치</div>`, lating: new kakao.maps.LatLng(lat, lon) }];

          for (const company of result) {
            geocoder.addressSearch(company.map, function (address, status) {
              if (status === kakao.maps.services.Status.OK) {
                positions.push({
                  content: `<div style="text-align:center;">${company.companyName}</div>`,
                  lating: new kakao.maps.LatLng(address[0].y, address[0].x),
                });
              }
              for (let i = 1; i <= positions.length; i++) {
                let marker = new kakao.maps.Marker({
                  map: map, // 마커를 표시할 지도
                  position: positions[i - 1].lating, // 마커의 위치
                });

                let infowindow = new kakao.maps.InfoWindow({
                  content: positions[i - 1].content, // 인포윈도우에 표시할 내용
                });

                kakao.maps.event.addListener(marker, 'mouseover', makeOverListener(map, marker, infowindow));
                kakao.maps.event.addListener(marker, 'mouseout', makeOutListener(infowindow));
              }
            });
          }
          ...

 

  • geocoder.addressSearch() 함수는 인수 address에 company.map의 정보를 불러온다.

  • address에 company.map의 정보가 정상적으로 들어 갔으면 status가 true가 되며 positions 객체에 정보를 넣는다. 

  • positions 에는 마커에 들어갈 정보들을 객체형태로 배열에 저장하였다.

  • positions의 [0] 번째 저장된 정보는 내 위치에 해당하는 마커 정보로 geolocation을 통해 구한
    위도와 경도의 정보가 들어있다.

문제 발생 및 문제 해결

  • geocoder.addressSearch() 함수는 비동기 함수이기 때문에 모든 주소 검색 요청이
    병렬도 시작되어 결과가 순서대로 반환되지 않는다.

  • 따라서 positions 배열에 올바른 순서로 데이터를 채우지 못하는 경우가 발생하여,
    정상적으로 for문을 통해 positions 배열에 객체를 push 해도 누락되는 데이터가 발생한다.

  • 오류 내용 : Uncaught (in promise) TypeError: Cannot read properties of undefined

 

문제 해결 1 - 실패

  • 비동기 함수를 순서대로 실행하게 하기 위해
  • asnyc, await을 사용하였다.
function currentLocation() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(async function (position) {
          // Geolocation 접속 위치 불러오기
          
          ....
          
           await geocoder.addressSearch(company.map, function (address, status) { ...

 

  • 하지만 geocoder.addressSearch 함수는 비동기 함수이기 때문에
    사용해도 요청이 병렬도 시작되기 때문에 효과가 없었다.

 

문제 해결 2 - 성공

  • for..of 반복문을 사용하여 비동기 요청을 순차적으로 실행시켰다.

for (const company of result) {
            await geocoder.addressSearch(company.map, function (address, status) {
              if (status === kakao.maps.services.Status.OK) {
                positions.push({
                  content: `<div style="text-align:center;">${company.companyName}</div>`,
                  lating: new kakao.maps.LatLng(address[0].y, address[0].x),
                });
                console.log(positions.length);
              }
              for (let i = 1; i <= positions.length; i++) {
                let marker = new kakao.maps.Marker({
                  map: map, // 마커를 표시할 지도
                  position: positions[i - 1].lating, // 마커의 위치
                });

                let infowindow = new kakao.maps.InfoWindow({
                  content: positions[i - 1].content, // 인포윈도우에 표시할 내용
                });

                kakao.maps.event.addListener(marker, 'mouseover', makeOverListener(map, marker, infowindow));
                kakao.maps.event.addListener(marker, 'mouseout', makeOutListener(infowindow));
              }
            });
          }

 

  • 각 주소 검색 요청을 완료할 때가지 기다려지기 때문에 비동기 요청이여도 순차적으로 실행하여
    올바른 순서로 데이터를 처리할 수 있었고, positions 배열에 데이터가 정상적으로 추가되었다.

  • console.log 를 통해 positions 배열의 길이를 확인하였다.

HTML_Console

 

  • for 반복문을 이용했을 때는 순서가 뒤죽박죽이거나, 위의 오류 메세지가 발생했는데
    for .. of 반복문을 통해 해결된 모습이다.

 

전체 코드 ( JS )

document.addEventListener('DOMContentLoaded', function () {
  axios.get('/api/company').then(function (response) {
    const result = response.data;

    function currentLocation() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(async function (position) {
          // Geolocation 접속 위치 불러오기

          let lat = position.coords.latitude; //위도
          let lon = position.coords.longitude; //경도

          let mapContainer = document.getElementById('map'), // 지도를 표시할 div
            mapOption = {
              center: new kakao.maps.LatLng(lat, lon), // 지도의 중심좌표
              level: 5, // 지도의 확대 레벨
            };

          const map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다
          const geocoder = new kakao.maps.services.Geocoder(); // 주소 좌표 변환 객체 생성

          let positions = [{ content: `<div">내 위치</div>`, lating: new kakao.maps.LatLng(lat, lon) }];

          for (const company of result) {
            await geocoder.addressSearch(company.map, function (address, status) {
              if (status === kakao.maps.services.Status.OK) {
                positions.push({
                  content: `<div style="text-align:center;">${company.companyName}</div>`,
                  lating: new kakao.maps.LatLng(address[0].y, address[0].x),
                });
                //console.log(positions.length);
              }
              for (let i = 1; i <= positions.length; i++) {
                let marker = new kakao.maps.Marker({
                  map: map, // 마커를 표시할 지도
                  position: positions[i - 1].lating, // 마커의 위치
                });

                let infowindow = new kakao.maps.InfoWindow({
                  content: positions[i - 1].content, // 인포윈도우에 표시할 내용
                });

                kakao.maps.event.addListener(marker, 'mouseover', makeOverListener(map, marker, infowindow));
                kakao.maps.event.addListener(marker, 'mouseout', makeOutListener(infowindow));
              }
            });
          }

          function makeOverListener(map, marker, infowindow) {
            return function () {
              infowindow.open(map, marker);
            };
          }

          // 인포윈도우를 닫는 클로저를 만드는 함수입니다
          function makeOutListener(infowindow) {
            return function () {
              infowindow.close();
            };
          }
        });
      }
      return true;
    }
    currentLocation(); // 지도 불러오기
  });
});

 

 

반응형