kakao map API를 이용하여 마커와 선을 이어보아요📍

수정·2024년 1월 5일
0

React

목록 보기
7/14
post-thumbnail

최근 진행했던 서울산책 서비스는 지도를 가진 기능들이 많았다.

  1. 현 위치 인식 후 주변 갈만한 장소 불러오기
  2. 서울특별시 자치구 선택 시 주변 갈만한 장소 불러오기
  3. 상세 페이지의 장소 위치 보여주기
  4. 미래유산코스 카테고리 페이지의 상세 장소들에 대한 코스 보여주기

오늘은 4번 장소들에 대한 코스가 어떻게 이어지는지 보여주는 지도 기능에 대해 정리해보려고 한다.

참고로 나는 이번 프로젝트에서 지도 기능들을 만들 때, kakao map API를 활용하여 제작했다.


🗺️ 코스 API 응답의 형태

내가 사용하게 될 데이터는 응답의 places 배열 부분이었다.
이 배열을 활용하여 order_number 순서대로 마커를 생성하고 선을 이어줘야 했다!

🗺️ 코드를 작성해보자

kakao map API를 세팅해놨다는 가정하에 정리해보려고 한다.

React HTML 코드

const CourseExplainPage = () => {
	/* 생략 */

  return (
    /* 생략 */

    <li style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
       <CourseMapButton type="button" onClick={() => isFindAddress(!findAddress)}>
       {findAddress ? '코스 지도 닫기' : '코스 지도 열기'} // 지도를 열고 닫고 할 수 있는 버튼
       </CourseMapButton>
       {findAddress ? <CourseRouteMap id="map" /> : null} // 지도
    </li>
    /* 생략 */
  )
	/* 생략 */
}

지도에 사용할 상태값과 hook

const CourseExplainPage = () => {
	const [findAddress, isFindAddress] = useState(false);
  
  	const { isLoading, data } = useGetInfoByCourseId(Number(location.pathname.match(/\/course\/(\d+)/)?.[1])))
  	const courseInfo = data?.data.result as CourseType;
    const coursePlaceInfo = courseInfo?.places;
    if (coursePlaceInfo) {
    	coursePlaceInfo.sort((a,b) => a.order_number - b.order_number);
    }
  
  	useEffect(() => {
  		if (findAddress && courseInfo && coursePlaceInfo) {
        	/* 지도와 관련된 코드들 */
        }
      
    }, [findAddress])
  
	/* 생략 */
}

지도와 관련된 코드를 작성하기 위해 findAddress 상태값과 courseInfo, coursePlaceInfo 데이터 그리고 useEffect 훅을 사용하려고 한다.

data 는 내가 만들어둔 API 훅을 이용하여 받아온다는 것을 참고하고 봐주면 좋을 것 같다!

  • findAddress, useEffect : 지도를 열고 닫는 상태값을 이용하여 지도에 마커를 표시할 수 있도록 함
  • courseInfo : data의 data.result 값을 담은 값
  • coursePlaceInfo : courseInfo의 places 배열을 받아와 order_number를 오름차순으로 덧씌워줄 값

useEffect hook 내부 코드 1 : 마커 이미지 넣기

useEffect(() => {
	window.kakao.maps.load(() => {
    	const mapContainer = document.getElementById('map');
      
      	const locationImageInfo = {
        	imageSrc: Marker, // 본인의 마커 이미지 값
          	imageSize: new window.kakao.maps.Size(24, 24) // 마커 이미지 사이즈
        };
      
      	const locationImage = new window.kakao.maps.MarkerImage(
        	locationImageInfo.imageSrc,
          	locationImage.imageSize
        );
      
      /* 아래에서 더 이어질 예정 */
    })

}, [findAddress]);

지도에 표시하고 싶은 마커 이미지를 불러와 imageSrc에 넣고, 사이즈도 지정해준다!

window.kakao.maps.MarkerImage 생성자를 이용하여 저장해두었던 마커 정보를 다시 넣어 추후 아래에서 마커를 생성하는 함수를 만들 때 다시 값을 사용할 수 있도록 만들어준다.

useEffect hook 내부 코드 2 : 마커를 생성하고 서로 잇는 함수 만들기

    if (findAddress && courseInfo && coursePlaceInfo) {
      window.kakao.maps.load(() => {
		/* 위 코드 생략 */

        // 마커 생성과 마커를 잇는 함수
        const drawMarkerAndLine = (mapInstance: any) => {
          const markers: any = [];
          const linePath: any = [];

          coursePlaceInfo.forEach(course => {
            const markerPosition = new window.kakao.maps.LatLng(
              course.place_latitude,
              course.place_longitude,
            );
            const marker = new window.kakao.maps.Marker({
              position: markerPosition,
              image: locationImage,
            });

            // order_number를 표시할 customOverlay 생성
            const orderNumberOverlay = new window.kakao.maps.CustomOverlay({
              position: markerPosition,
              content: `<div style="width: 50px; height: 25px; background-color: #fff; border: 1px solid #19bb35;
                border-radius: 10px; font-size: 12px; font-weight: bold; text-align: center; 
                position: absolute; bottom: 20px; left: -24px; z-index: 5;" >
                  ${course.order_number + 1} 코스
                </div>`,
            });

            markers.push(marker);
            linePath.push(markerPosition);
            orderNumberOverlay.setMap(mapInstance);
          });

          markers.map((item: any) => item.setMap(mapInstance));
          
          // kakao map API에서 제공하는 Polyline 함수
          const polyline = new window.kakao.maps.Polyline({
            path: linePath,
            strokeWeight: 3,
            strokeColor: '#001aff',
            strokeOpcaity: 1,
            strokeStyle: 'solid',
          });

          polyline.setMap(mapInstance);
        };

        // 코스 루트를 표시할 지도의 중앙값 계산!
        const calculateMapCenter = (places: CoursePlaceType[]) => {
          const latitudes = places.map(place => parseFloat(place.place_latitude));
          const longitudes = places.map(place => parseFloat(place.place_longitude));

          const averageLatitude =
            latitudes.reduce((sum, value) => sum + value, 0) / latitudes.length;
          const averageLongitude =
            longitudes.reduce((sum, value) => sum + value, 0) / longitudes.length;

          return new window.kakao.maps.LatLng(averageLatitude, averageLongitude);
        };

        const center = calculateMapCenter(coursePlaceInfo);
        const options = {
          center: center,
          level: 5,
        };

        const map = new window.kakao.maps.Map(mapContainer, options);
        drawMarkerAndLine(map); // 지도 생성 후 마커-선 표시 함수 실행

        return map;
      });
    }

coursePlaceInfo 배열의 길이만큼 반복하며, 마커를 생성하고 배열에 담아준다.
그리고 마커의 위치값을 linePath 배열에 담아 Polyline 함수를 활용할 때 또 사용할 예정이다.
어떤 코스의 마커인지 정보를 넣기 위해 커스텀오버레이도 추가했다.

마커 간 선을 잇기 위해서는 kakao map API의 Polyline 메소드를 사용하면 된다.

사용법은 다음과 같다.

const polyline = new window.kakao.maps.Polyline({
	path: 장소들의 위치값(위도, 경도)이 담긴 배열
	strokeWeight: 선의 두께값
    strokeColor: 선의 색상
    strokeOpacity: 선의 투명도
    strokeStyle: 선 스타일
})

polyline.setMap(mapInstance);

Polyline이 가진 속성값은 더 다양해서, 필요한 게 있다면 공식문서를 참고해보면 좋을 것 같다~

그리고 코스의 위치가 모두 떨어져있기 때문에, coursePlaecInfo에 담긴 모든 장소들의 중앙값을 계산해야 했다.
calculateMapCenter 함수를 통해 코스 루트를 표시할 지도의 중앙값을 계산하여 map의 center값으로 지정했다!

🗺️ 결과물

한 코스 페이지에 들어와 예시본을 캡쳐했다.
이렇게 유저가 코스 지도를 열게 되면 코스 순서대로 마커를 표시하고, 선을 잇게되어 코스를 한 눈에 볼 수 있게 된다!

이걸 구현하며 약간의 아쉬웠던 점은,
원래는 해당 페이지들에 들어오면 지도가 바로 보이도록 하고 싶었는데 그렇게 구현하려고 하니 useEffect와 관련된 코드가 의도대로 수정되질 않고, 엉키는 게 생기는 것 같아 쉽게 구현할 수가 없었다..😭

시간이 부족하여 어쩔 수 없이 유저가 궁금할때 열고 닫고 할 수 있는 지도로 구현했는데, 다음에는 원래 의도대로 만들고 싶다는 생각이 든다!

profile
💛

0개의 댓글