Skip to content

[페이지별] 교통 정보

minjee edited this page Sep 23, 2023 · 30 revisions

교통 정보

교통 API

카카오 맵 API를 불러옵니다.

지도 생성

useEffect(() => {
    if (window.kakao && window.kakao.maps) {
      const container = document.getElementById('mapContainer');
      const options = {
        center: new window.kakao.maps.LatLng(33.5068, 126.4929),
        level: 5,
      };

      const map = new window.kakao.maps.Map(container, options);
    }
  }, []);

useEffect 훅을 사용하여 컴포넌트가 마운트될 때 카카오 맵을 초기화하고 렌더링합니다.

  • 초기 중심 좌표로 위도 33.5068, 경도 126.4929(제주 공항)를 설정하여 사용자가 페이지에 처음 접속했을 때 제주 공항이 보이도록 했습니다.

교통 정보 표시 및 확대 축소 컨트롤러

mapRef.current = map;

map.addOverlayMapTypeId(window.kakao.maps.MapTypeId.TRAFFIC);

const zoomControl = new window.kakao.maps.ZoomControl();
map.addControl(zoomControl, window.kakao.maps.ControlPosition.RIGHT);

useEffect 안에 추가되어 교통 정보를 표시하고, 사용자가 지도의 확대/축소를 조절할 수 있는 컨트롤을 제공합니다.

  • map.addOverlayMapTypeId(...): 지도 위에 교통 정보(예: 도로 상황)를 표시하는 데 사용됩니다.
  • const zoomControl ...: 이 두 줄의 코드는 화면 오른쪽에 위치한 확대/축소 컨트롤을 생성하고, 그것을 지도에 추가합니다.

지도 위치 검색

검색어를 입력하면 결과를 출력하고 Input을 초기화합니다.

검색어 입력

function searchPlaces() {
    if (!window.kakao || !window.kakao.maps || !mapRef.current) return;

    const places = new window.kakao.maps.services.Places();

    places.keywordSearch(keyword, function (result, status) {
      removeMarkers();

      let tempMarkers = [];

      if (status === window.kakao.maps.services.Status.OK) {
        for (let i = 0; i < result.length; i++) {
          let markerInfo = displayMarker(result[i]);
          tempMarkers.push(markerInfo);
        }

        setMarkers(tempMarkers);

        mapRef.current.setLevel(8);
      }
    });

    setKeyword('');

사용자가 입력한 키워드를 바탕으로 장소를 검색하고, 그 결과를 처리합니다.

  • window.kakao.maps.services.Places()를 이용하여 Places 객체를 생성합니다. 그리고 해당 객체의 keywordSearch 메서드를 호출하여 키워드에 대한 검색을 수행합니다.
  • 검색 결과는 콜백 함수 내부에서 처리되며, 여기서 기존 마커들을 제거하고 새로운 마커들을 생성하여 지도 위에 표시하고 줌 레벨을 조정합니다.
  • setKeyword('')에서 입력 필드를 초기화하여 다음 검색 준비를 합니다.
<div className="absolute top-5 left-5 flex gap-2 z-10">
  <input
    type="text"
    value={keyword}
    className="w-[25vw] px-5 py-3 border-[3px] border-white rounded-lg"
    placeholder="장소 검색"
    onChange={(e) => setKeyword(e.target.value)}
    onKeyPress={handleKeyPress}
  />
  <button
    className="w-[5vw] bg-blue text-white border-0 rounded-lg"
    onClick={searchPlaces}
  >
  검색
  </button>
</div>

버튼에 onClick을 사용해 searchPlaces 함수를 적용했습니다.

function handleKeyPress(event) {
  if (event.key === 'Enter') {
    searchPlaces();
  }
}

Input에 적용시켜 검색어를 입력 후 엔터키로도 검색이 가능하도록 했습니다.

마커 생성하기

지도에 마커를 생성하고 마커 클릭 시 이동합니다.

마커 표시하기

function displayMarker(place) {
  const marker = new window.kakao.maps.Marker({
    position: new window.kakao.maps.LatLng(place.y, place.x),
    map: mapRef.current,
  });

  const infowindow = new window.kakao.maps.InfoWindow({ zIndex: 1 });

  window.kakao.maps.event.addListener(marker, 'mouseover', function () {
    infowindow.setContent(
      '<div className="p-1 text-sm">' + place.place_name + '</div>'
    );
    infowindow.open(mapRef.current, marker);
  });

  return marker;
}

주어진 장소 정보(place)를 바탕으로 지도 위에 마커를 생성하고 표시합니다. 각 마커는 해당 장소의 이름을 보여주는 정보창과 연결되어 있습니다. 마커를 클릭 시 infowindow를 표시해 줍니다.

마커로 이동

window.kakao.maps.event.addListener(marker, 'click', function () {
  mapRef.current.setCenter(marker.getPosition());

   mapRef.current.setLevel(1);
});

각 마커에 'click' 이벤트 리스너를 추가하여, 클릭 시 해당 위치로 지도 중심을 이동하고 줌 레벨을 변경합니다. 마커를 클릭 시 마커 위치를 중심으로 이동하고 5 레벨에서 1 레벨로 확대해 위치를 조금 더 자세히 보여 줍니다.

마커 초기화

function removeMarkers() {
 for (let i = 0; i < markers.length; i++) {
   markers[i].setMap(null);
 }
 setMarkers([]);
}

현재 지도 위에 표시된 모든 마커들을 제거합니다. 새로운 검색이 실행될 때마다 이 함수를 호출하여 지도를 초기화합니다.

결과물

화면-기록-2023-09-23-오후-10 29 23

전체 코드

import { useEffect, useRef, useState } from 'react';

export default function GetKakaoMap() {
  const mapRef = useRef(null);
  const [keyword, setKeyword] = useState('');
  const [markers, setMarkers] = useState([]);

  // 카카오 맵 생성하기
  useEffect(() => {
    if (window.kakao && window.kakao.maps) {
      const container = document.getElementById('mapContainer');
      const options = {
        center: new window.kakao.maps.LatLng(33.5068, 126.4929),
        level: 5,
      };

      const map = new window.kakao.maps.Map(container, options);

      mapRef.current = map;

      map.addOverlayMapTypeId(window.kakao.maps.MapTypeId.TRAFFIC);

      const zoomControl = new window.kakao.maps.ZoomControl();
      map.addControl(zoomControl, window.kakao.maps.ControlPosition.RIGHT);
    }
  }, []);

  function searchPlaces() {
    if (!window.kakao || !window.kakao.maps || !mapRef.current) return;

    const places = new window.kakao.maps.services.Places();

    places.keywordSearch(keyword, function (result, status) {
      removeMarkers();

      let tempMarkers = [];

      if (status === window.kakao.maps.services.Status.OK) {
        for (let i = 0; i < result.length; i++) {
          let markerInfo = displayMarker(result[i]);
          tempMarkers.push(markerInfo);
        }

        setMarkers(tempMarkers);

        mapRef.current.setLevel(8);
      }
    });

    setKeyword('');

    function displayMarker(place) {
      const marker = new window.kakao.maps.Marker({
        position: new window.kakao.maps.LatLng(place.y, place.x),
        map: mapRef.current,
      });

      const infowindow = new window.kakao.maps.InfoWindow({ zIndex: 1 });

      window.kakao.maps.event.addListener(marker, 'mouseover', function () {
        infowindow.setContent(
          '<div className="p-1 text-sm">' + place.place_name + '</div>'
        );
        infowindow.open(mapRef.current, marker);
      });

      window.kakao.maps.event.addListener(marker, 'click', function () {
        mapRef.current.setCenter(marker.getPosition());

        mapRef.current.setLevel(1);
      });

      window.kakao.maps.event.addListener(marker, 'mouseout', function () {
        infowindow.close();
      });

      return marker;
    }

    function removeMarkers() {
      for (let i = 0; i < markers.length; i++) {
        markers[i].setMap(null);
      }
      setMarkers([]);
    }
  }

  function handleKeyPress(event) {
    if (event.key === 'Enter') {
      searchPlaces();
    }
  }

  return (
    <div className="relative border-4 border-blue w-[80%] h-[60vw]">
      <div id="mapContainer" className="w-full h-full relative">
        <div id="map" className="w-full h-full" />
      </div>

      <div className="absolute top-5 left-5 flex gap-2 z-[9]">
        <input
          type="text"
          value={keyword}
          className="w-[25vw] px-5 py-3 border-[3px] border-white rounded-lg"
          placeholder="장소 검색"
          onChange={(e) => setKeyword(e.target.value)}
          onKeyPress={handleKeyPress}
        />
        <button
          className="w-[5vw] bg-blue text-white border-0 rounded-lg"
          onClick={searchPlaces}
        >
          검색
        </button>
      </div>
    </div>
  );
}