Next.js + TS | Kakao 지도 API 프로젝트에 사용하기 #5 장소 위치 지도에 표시하기 (마커, 커스텀오버레이)

dayannne·2024년 9월 8일
0
post-thumbnail
post-custom-banner

Next.js + TS | Kakao 지도 API 프로젝트에 사용하기 #4. 키워드로 장소 검색하기에서 가져온 장소의 위치를 지도에 표시해줄 차례이다.


샘플 코드

장소 검색 결과를 잘 받아온 후 장소의 위치를 지도에 찍어주고 싶다고 할 때,
Kakao API에서 사용할 수 있는 객체로는 마커, 인포윈도우, 커스텀오버레이 이렇게 3개가 있다.

기본 마커인포윈도우커스텀 오버레이
MarkerInfoWindowCustomOverlay
기본 마커 (이미지 클릭 시 문서 이동)인포윈도우 (이미지 클릭 시 문서 이동)커스텀 오버레이 (이미지 클릭 시 문서 이동)

Kakao 지도 API 문서에서 설명하는 코드 예시를 보면 다음과 같이 구현할 수 있다.

// MAP 객체 생성 시 들어갈 설정
var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

// MAP 객체 
var map = new kakao.maps.Map(mapContainer, mapOption);
var position  = new kakao.maps.LatLng(33.450701, 126.570667); 
var content = ''

// 마커 띄우기
var marker = new kakao.maps.Marker({
    position
});

// 인포윈도우
var infowindow = new kakao.maps.InfoWindow({
    position, 
    content
});
// 커스텀 오버레이
var customOverlay = new kakao.maps.CustomOverlay({
    position, 
    content
});
 marker.setMap(map); // 마커 띄우기
infowindow.open(map, marker); 
customOverlay.setMap(map); // 커스텀 오버레이 띄우기
  1. Map 객체 생성 / 혹은 생성되어 있음 전제
  2. position 값 가져오기 (코드에선 지정된 position을 사용하고 있으나, 이후 장소 데이터 각각의 position 값을 불러올 것임.)
  3. Marker, InfoWindow, CustomOverlay 객체 생성
  4. setMap() 메서드를 통해 지도에 띄우기

    ⭐ 객체 정의 + setMap()메서드를 사용하지 않고 parameter에 map을 넣어 객체를 '생성'만 하는 방식으로도 충분히 요소를 지도에 띄울 수 있다.

    new kakao.maps.Marker({
        map,
        position
    });

    ⭐ 인포윈도우의 경우
    - 마커와 함께 사용 시에는 open()메서드를 통해 띄운다.

tip) 각 기능 사용 시 특징

위를 바탕으로 커스텀하기 위해 세개를 모두 사용해본 결과, 각각의 제한이 존재했다.

  • 마커
    • option에서 Dom Element 삽입을 지원하지 않음. 단, markerImage 옵션이 존재해 이미지 파일을 적용하는 방식은 가능
  • 인포윈도우
    • content옵션을 통해 Dom Element 삽입이 가능하나, 이미지처럼 인포 윈도우 최상위 element에서 고정된 스타일이 있어 커스텀이 어려움(이미지 처럼 테두리 하얀 네모박스...못없앱니다...)
  • 커스텀 오버레이 : 고정 스타일 없음! Dom Element 삽입가능!

결론적으로 커스텀을 하고 싶다면 인포윈도우 사용은 좋지 않았다.

  1. 마커 표시만 하고 싶다면, 마커를 사용하고,
  2. 마커 위에 장소의 정보를 띄우고 싶다면 커스텀 오버레이로 대체하되
    열고 닫기 기능을 추가할 경우 마커에 이벤트를 등록하여 '클릭 시 커스텀 오버레이 생성'하는 방법으로 구현하는 것을 추천한다.

나의 경우 프로젝트에서 필요에 따라 각각 마커커스텀 오버레이를 사용하여 다음과 같이 장소 위치를 띄워주고 있는데, 그 방법을 얘기해보려 한다.

- Marker 사용 -CustomOverlay 사용

마커 / 커스텀오버레이 띄우기 준비물

Next.js + TS | Kakao 지도 API 프로젝트에 사용하기 #4. 키워드로 장소 검색하기에 만들었던 useSearchPlaces를 가져왔다.

const useSearchPlaces = () => {

  // 장소 검색 함수
  const searchPlaces = (keyword: string) => {
    if (keyword !== '') {
      const places = new kakao.maps.services.Places();
      places.keywordSearch(keyword, searchPlacesCB);
    }
  };
  
  // keywordSearch 콜백 함수
  const searchPlacesCB = async (
    data: any,
    status: kakao.maps.services.Status,
    pagination: any,
  ) => {
    if (status === kakao.maps.services.Status.OK) {
      console.log(filteredPlaces);
    } else {
      if (status === kakao.maps.services.Status.ZERO_RESULT) {
        return alert('검색 결과가 존재하지 않습니다.');
      } else if (status === kakao.maps.services.Status.ERROR) {
        return alert('검색 결과 중 오류가 발생했습니다.');
      }
    }
  };

  return { searchPlaces };
};

export default useSearchPlaces;
  1. 여기에 Map 객체를 가져오고,
  2. 장소 검색 결과로 반환된 데이터의 위치 데이터를 이용해
    Marker / Custoverlay 객체를 생성하는 함수를 만들어야 한다.

1번은 #2 지도 띄우기를 구현할 때 mapContext에 미리 저장한 Map 객체를 활용하고,
2번은 displayPlaces라는 함수를 만들어 객체 생성을 처리해볼 것이다.

그러면 아래와 같이 세팅 완!

const useSearchPlaces = () => {
  const mapContext = useMap(); // mapContext 가져오기
  
  // 장소 검색 함수
  const searchPlaces = (keyword: string) => {
    if (keyword !== '') {
      const places = new kakao.maps.services.Places();
      places.keywordSearch(keyword, searchPlacesCB);
    }
  };
  
  // keywordSearch 콜백 함수
  const searchPlacesCB = async (
    data: any,
    status: kakao.maps.services.Status,
    pagination: any,
  ) => {
    if (status === kakao.maps.services.Status.OK) {
      displayPlaces(filteredPlaces); // 마커 / 커스텀오버레이를 생성하는 함수 호출
    } else {
      if (status === kakao.maps.services.Status.ZERO_RESULT) {
        return alert('검색 결과가 존재하지 않습니다.');
      } else if (status === kakao.maps.services.Status.ERROR) {
        return alert('검색 결과 중 오류가 발생했습니다.');
      }
    }
  };

  // 마커 / 커스텀오버레이 생성
  const displayPlaces = (places: any[]) => {
  	const mapData : kakao.maps.Map = mapContext?.mapData;
    // 만약 Map을 띄움과 동시에 마커를 찍을 거라면 샘플 코드처럼
    // var map = new kakao.maps.Map(mapContainer, mapOption);를 정의해 사용
    
     places.forEach((place, index) => {
      const position = new kakao.maps.LatLng(place.y, place.x); // position 값 가져오기 
     
       // ...여기서 Marker,Customoverlay 등을 생성
     
     }
  }
  return { searchPlaces };
};

export default useSearchPlaces;

커스텀 마커(Marker) 띄우기

사진처럼 내가 커스텀해 만든 마커를 띄우는 방법으로 다른 이미지로 마커 생성하기를 활용하면 되는데,

kakao.maps.Marker(options)

주어진 객체로 마커를 생성한다.
지도 뿐만 아니라 로드뷰 위에도 올릴 수 있다.

var marker = new kakao.maps.Marker({
    map: map,
    position: new kakao.maps.LatLng(33.450701, 126.570667)
});

Parameters

  • options Object
    • map Map | Roadview : 마커가 올라갈 지도 또는 로드뷰
    • position LatLng | Viewpoint : 마커의 좌표 또는 로드뷰에서의 시점
    • image MarkerImage : 마커의 이미지
    • title String : 마커 엘리먼트의 타이틀 속성 값 (툴팁)
    • draggable Boolean : 드래그 가능한 마커, 로드뷰에 올릴 경우에는 유효하지 않다
    • clickable Boolean : 클릭 가능한 마커
    • zIndex Number : 마커 엘리먼트의 z-index 속성 값
    • opacity Number : 마커 투명도 (0-1)
    • altitude Number : 로드뷰에 올라있는 마커의 높이 값(m 단위)
    • range Number : 로드뷰 상에서 마커의 가시반경(m 단위), 두 지점 사이의 거리가 지정한 값보다 멀어지면 마커는 로드뷰에서 보이지 않게 된다

Marker 객체의 Parameters를 살펴보면 image옵션을 이용해 마커 이미지를 지정할 수 있고,
MarkerImage라는 객체를 받고 있다.

kakao.maps.MarkerImage(src, size[, options])

Parameters

  • src String : 이미지 주소
  • size Size : 마커의 크기
  • options Obejct
    • alt String : 마커 이미지의 alt 속성값을 정의한다.
    • coords String : 마커의 클릭 또는 마우스오버 가능한 영역을 표현하는 좌표값
    • offset Point : 마커의 좌표에 일치시킬 이미지 안의 좌표 (기본값: 이미지의 가운데 아래)
    • shape String : 마커의 클릭 또는 마우스오버 가능한 영역의 모양
    • spriteOrigin Point : 스프라이트 이미지 중 사용할 영역의 좌상단 좌표
    • spriteSize Size : 스프라이트 이미지의 전체 크기

MarkerImage의 src , size, options - offset를 사용하여 객체 정의 후,
Marker 객체를 생성하면 된다.

  // 마커 / 커스텀오버레이 생성
  const displayPlaces = (places: any[]) => {
  	const mapData : kakao.maps.Map = mapContext?.mapData;
    // 만약 Map을 띄움과 동시에 마커를 찍을 거라면 샘플 코드처럼
    // var map = new kakao.maps.Map(mapContainer, mapOption);를 정의해 사용
    
     places.forEach((place, index) => {
      const position = new kakao.maps.LatLng(place.y, place.x); // position 값 가져오기 
     
      // MarkerImage parameter
      const imageSrc = '/icons/icon-marker.svg',
        imageSize = new kakao.maps.Size(56, 56),
        imageOption = { offset: new kakao.maps.Point(27, 54) };
      // MarkerImage
      const markerImage = new kakao.maps.MarkerImage(
        imageSrc,
        imageSize,
        imageOption,
      );
      // Marker
      const marker = new kakao.maps.Marker({
        position: position,
        image: markerImage,
      });

      // 지도에 띄우기
      marker.setMap(mapContext?.mapData as kakao.maps.Map);
 
     }
  }

커스텀 오버레이 띄우기

커스텀 오버레이 사용 시 '장소 이름 데이터'를 사용하면서도 원하는 스타일링으로 장소 위치를 띄울 수 있다.

kakao.maps.CustomOverlay(options)

주어진 객체로 커스텀 오버레이를 생성한다.

var customOverlay = new kakao.maps.CustomOverlay({
    map: map,
    clickable: true,
    content: '<div class="customOverlay"><a href="#">Chart</a></div>',
    position: new kakao.maps.LatLng(33.450701, 126.570667),
    xAnchor: 0.5,
    yAnchor: 1,
    zIndex: 3
});

Parameters

  • `options Object
    • clickable Boolean : true 로 설정하면 컨텐츠 영역을 클릭했을 경우 지도 이벤트를 막아준다.
    • content Node | String : 엘리먼트 또는 HTML 문자열 형태의 내용
    • map Map | Roadview : 커스텀 오버레이가 올라갈 지도 또는 로드뷰
    • position LatLng | Viewpoint : 커스텀 오버레이의 좌표 또는 로드뷰에서의 시점
    • xAnchor Number : 컨텐츠의 x축 위치. 0_1 사이의 값을 가진다. 기본값은 0.5
    • yAnchor Number : 컨텐츠의 y축 위치. 0_1 사이의 값을 가진다. 기본값은 0.5
    • zIndex Number : 커스텀 오버레이의 z-index

contentNode를 받고 있어, 컴포넌트를 HTMLElement으로 변환해 넣어 줌으로써 커스텀이 가능하다.

content에 들어갈 컴포넌트 만들기

이미지와 같은 말풍선 모양의 마커를 구현하기 위해 장소 이름(placeName)을 받는 MarkerInfo라는 컴포넌트를 만들었다.

'use client';

import Image from 'next/image';

interface MarkerInfoProps {
  placeName: string;
}

const MarkerInfo = ({ placeName }: MarkerInfoProps) => {
  return (
    <div className='speech-bubble flex w-auto gap-2 px-3 py-2 text-sm font-medium lg:text-lg'>
      <span>{placeName}</span>
      <Image
        className='max-w-none'
        width={20}
        height={20}
        src='/icons/icon-logo-mini(default).svg'
        alt='로고 그림'
      />
    </div>
  );
};

export default MarkerInfo;

이를 String으로 변환해 CustomOverlay 객체 생성 시 content에 지정해 주면 된다.

import MarkerInfo from '../_component/common/MarkerInfo';

  //...

  // 마커 / 커스텀오버레이 생성
  const displayPlaces = (places: any[]) => {
  	const mapData : kakao.maps.Map = mapContext?.mapData;
    // 만약 Map을 띄움과 동시에 마커를 찍을 거라면 샘플 코드처럼
    // var map = new kakao.maps.Map(mapContainer, mapOption);를 정의해 사용
    
     places.forEach((place, index) => {
      const position = new kakao.maps.LatLng(place.y, place.x); // position 값 가져오기 
     
       // 커스텀오버레이 content에 들어갈 컴포넌트
      const overlayContent = <MarkerInfo placeName={place.place_name} />;
      // 컴포넌트를 string으로 전환 후 HTMLElement에 삽입
      const overlay = document.createElement('div');
      overlay.innerHTML = ReactDOMServer.renderToString(overlayContent);
	  // 커스텀 오버레이 생성
      const newOverlay = new kakao.maps.CustomOverlay({
        position: position,
        content: overlay,
        yAnchor: 1.3, // 높이 지정 (선택)
      });

      newOverlay.setMap(mapContext?.mapData as kakao.maps.Map);
 
     }
  }

마커/커스텀 오버레이에 클릭 이벤트 등록하기

생성과 동시에 클릭 이벤트를 등록해 줌으로써 해당 마커의 위치로 이동하거나, 장소 상세 페이지로 이동하는 등의 기능을 넣어줄 수 있다.
아래는 커스텀오버레이 클릭 시 지도를 해당 마커의 위치로 이동하는 이벤트를 등록한 방법이다.

import MarkerInfo from '../_component/common/MarkerInfo';

  //...

  // 마커 / 커스텀오버레이 생성
  const displayPlaces = (places: any[]) => {
  	const mapData : kakao.maps.Map = mapContext?.mapData;
    // 만약 Map을 띄움과 동시에 마커를 찍을 거라면 샘플 코드처럼
    // var map = new kakao.maps.Map(mapContainer, mapOption);를 정의해 사용
    
     places.forEach((place, index) => {
      const position = new kakao.maps.LatLng(place.y, place.x); // position 값 가져오기 
     
       // 커스텀오버레이 content에 들어갈 컴포넌트
      const overlayContent = <MarkerInfo placeName={place.place_name} />;
      // 컴포넌트를 string으로 전환 후 HTMLElement에 삽입
      const overlay = document.createElement('div');
      overlay.innerHTML = ReactDOMServer.renderToString(overlayContent);

     // HTMLElement에 이벤트 등록
     overlay.addEventListener('click', () => {
        mapContext?.mapData?.panTo(position);
      });
       
	  // 커스텀 오버레이 생성
      const newOverlay = new kakao.maps.CustomOverlay({
        position: position,
        content: overlay,
        yAnchor: 1.3, 
        clickable: true, // 클릭 가능 여부 true로 설정
      });

      newOverlay.setMap(mapContext?.mapData as kakao.maps.Map);
 
     }
  }

profile
☁️
post-custom-banner

0개의 댓글