Google Maps API를 사용한 React/Next 지도 구현 및 사용자 위치 표시하기

zooyaho·2024년 7월 5일
0
post-thumbnail
post-custom-banner

Google Maps API와 React를 사용하여 사용자 위치를 지도에 표시하고 클러스터링을 통해 마커를 그룹화하는 방법을 다룹니다. 프로젝트를 설정하고 필요한 라이브러리를 설치한 후, 지도 컴포넌트를 생성하고 사용자 위치를 표시하는 방법을 단계별로 설명하겠습니다.


1. @react-google-maps/api

설치

npm install @react-google-maps/api
  • Weekly Downloads 수도 많고 최근까지 업데이트를 진행하고 있어 해당 라이브러리를 선택하였습니다.

2. Google Maps API 키 생성

Google Cloud Platform에서 프로젝트를 생성하고 Google Maps API를 활성화한 후, API 키를 발급받습니다. 이 키는 .env 파일에 저장하여 환경 변수를 통해 사용합니다.

.env 파일 생성

REACT_APP_GOOGLE_MAPS_API_KEY=YOUR_API_KEY_HERE

3. 기능

✅ 커스텀 마커
✅ 클러스터 설정 및 커스텀
✅ 한반도 기준으로 줌 설정
✅ 지도 이동, 확대/축소 막기
✅ 마커 클릭 시 마커 중심으로 확대
✅ 줌 초기화/ 이전 줌 이동/ 현재 위치로 줌 버튼

4. Google Maps 컴포넌트 설정

import { GoogleMap, LoadScriptNext } from '@react-google-maps/api';
import { darkModeMapStyle } from './mapData';

// 맵 스타일
const mapContainerStyle = {
  width: '600px',
  height: '700px',
};

// 한국 중심 좌표
const koreaCenter = {
  lat: 35.9078,
  lng: 127.7669,
};

// 초기 줌 레벨
const initialZoom = 7.2;

const mapOptions = {
  disableDefaultUI: true,
  styles: darkModeMapStyle,
  backgroundColor: '#06080C',
};

function Map() {
  const mapRef = useRef(null);
  const [isMapLoaded, setIsMapLoaded] = useState(false);

  // 맵 로딩 완료 핸들러
  const onMapLoad = (map) => {
    mapRef.current = map;
    setIsMapLoaded(true);
  };

  // 줌 변경 핸들러
  const onZoomChanged = () => {
    // ...
  };
  
  return (
    <LoadScriptNext googleMapsApiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}>
      <>
        {!isMapLoaded && <LoadingFallback />}
        <GoogleMap
          mapContainerStyle={mapContainerStyle}
          center={koreaCenter}
          zoom={initialZoom}
          options={mapOptions}
          onLoad={onMapLoad}
          onZoomChanged={onZoomChanged}
         >
          {/* Markers, cluster 컴포넌트 작업 */}
      </>
    </ LoadScript>
  );}

LoadScriptNext 컴포넌트

  • 최근에 나온 LoadScriptNext 컴포넌트는 다중 지도 로드가 가능하며 성능 최적화를 제공합니다.
  • 발급받은 API 키를 googleMapsApiKey에 할당합니다.

GoogleMap 컴포넌트

  1. mapContainerStyle
  • 맵 컨테이너의 스타일 지정, 맵의 크기를 정의하는 객체
  1. center
  • 맵의 초기 중심 좌표를 설정. lat는 위도, lng는 경도를 의미
  • 예제에서는 한국의 중심 좌표를 사용했습니다.
  1. zoom
  • 맵의 초기 줌 레벨을 설정. 줌 레벨이 클수록 더 가까운 뷰를 제공합니다.
  1. options
  • 맵의 추가 옵션을 설정하는 객체입니다.
  • 기본 UI 요소를 비활성화(disableDefaultUI: true)하거나 맵 스타일을 커스터마이징할 수 있습니다.
  • https://mapstyle.withgoogle.com/ 사이트를 통해 원하는 스타일의 맵 만들고, 생성된 json 파일을 적용합니다.(예제에서는 다크 스타일 커스텀하여 적용)
  1. onLoad
  • 맵이 로드될 때 호출되는 콜백 함수입니다. 맵 객체를 참조하는 mapRef를 설정하고, 로딩 상태를 업데이트합니다.
  1. onZoomChanged
  • 맵의 줌 레벨이 변경될 때 호출되는 콜백 함수입니다. 줌 변경에 대한 추가 로직을 추가할 수 있습니다.



5. Marker/Cluster 설정

  • 클러스터는 마커가 많은 경우 이를 그룹화하여 지도상에서 더 깔끔하게 표시하는 기능입니다.

마커 위치 정보 설정

  • 마커가 위치할 좌표를 배열로 정의합니다.
const markerLocations = [
  { lat: 37.5743484, lng: 126.9897057 }, // 익선동
  { lat: 37.5717174, lng: 126.9860732 }, // 인사동
  { lat: 37.58152115, lng: 126.98487282 }, // 북촌한옥마을
  { lat: 36.78230587, lng: 127.22517054 }, // 독립기념관
  { lat: 37.58053486, lng: 127.00281143 }, // 추가된 좌표
  { lat: 35.17861405, lng: 129.19969082 }, // 송정해수욕장
];

클러스터 스타일 정의

  • 마커의 수에 따라 클러스터의 스타일을 정의합니다. MarkerClustererF에서 사용됩니다.
const clusterStyles = [
  {
    // 5개 이하
    textColor: 'white',
    url: '/icon/cluster1.svg',
    height: 50,
    width: 50,
    textSize: 18,
  },
  {
    // 5개 초과 10개 이하
    textColor: 'white',
    url: '/icon/cluster2.svg',
    height: 50,
    width: 50,
    textSize: 24,
  },
  {
    // 10개 초과 15개 이하
    textColor: 'white',
    url: '/icon/cluster3.svg',
    height: 70,
    width: 70,
    textSize: 24,
  },
  {
    // 15개 초과
    textColor: 'white',
    url: '/icon/cluster4.svg',
    height: 70,
    width: 70,
    textSize: 24,
  },
];

클러스터 계산기

  • 마커의 수에 따라 적절한 클러스터 스타일을 적용하기 위한 계산 로직입니다.
const clusterCalculator = (markers) => {
  let index = 0;
  const count = markers.length;

  if (count <= 5) {
    index = 1;
  } else if (count <= 10) {
    index = 2;
  } else if (count <= 15) {
    index = 3;
  } else {
    index = 4;
  }

  return {
    text: count.toString(),
    index: index,
  };
};

MarkerF, MarkerClustererF 적용

import { MarkerF, MarkerClustererF } from '@react-google-maps/api';

function Map() {
  const mapRef = useRef(null);

  /** 마커 클릭 이벤트 핸들러 */
  const onClickMarker = (marker: {
    lat: number;
    lng: number;
  }) => {
    if (mapRef.current) {
      // ...
      mapRef.current.setCenter({
        lat: marker.lat,
        lng: marker.lng,
      });
      mapRef.current.setZoom(15);
    }
  };
  
  return (
    // ...
     <GoogleMap // ... 
     >
       <MarkerClustererF
         options={{ styles: clusterStyles, calculator: clusterCalculator }}
         >
         {(clusterer) => (
           <>
             {markerLocations.map((marker, index) => (
               <MarkerF
                 key={index}
                 position={{
                   lat: marker.lat,
                   lng: marker.lng,
                 }}
                 clusterer={clusterer}
                 icon={{ url: '/icon/marker.svg', scale: 2 }}
                 onClick={() => onClickMarker(marker)}
                />
             ))}
             </>
         )}
      </MarkerClustererF>
      {/* 사용자 위치 마커 */}
      {userLocation && (
        <MarkerF
          position={userLocation}
          icon={{ url: '/icon/user-location.svg', scale: 7 }}
         />
      )}
     </GoogleMap>
  );}
  1. MarkerClustererF 설정
  • options 속성에 styles와 calculator를 설정합니다.
  • styles는 클러스터 스타일을 정의하며, calculator는 마커 수에 따라 적절한 클러스터 스타일을 적용합니다.
  • {(clusterer) => ...} 내부에서 클러스터링된 마커를 설정합니다.
  1. MarkerF 설정
  • React18버전부터는 Marker컴포넌트가 아닌 MarkerF컴포넌트를 사용해야 새로고침 시 마커가 보이지 않는 현상이 발생하지 않습니다!!
  • markerLocations.map()을 통해 각 위치에 마커를 생성합니다.
  • position 속성으로 마커의 위치를 설정합니다.
  • clusterer 속성을 통해 클러스터러에 마커를 추가합니다.
  • icon 속성으로 마커의 아이콘을 설정합니다.
  • onClick 이벤트 핸들러를 통해 마커 클릭 시 지도의 중심과 줌 레벨을 변경합니다.
  1. 사용자 위치 마커
  • userLocation 상태가 설정되어 있을 경우, 사용자 위치에 마커를 추가합니다.
  • 사용자 위치 마커는 다른 마커보다 크게 설정하여 눈에 잘 띄게 합니다.



6. 줌 초기화/ 이전 줌 이동/ 현재 위치로 줌 버튼

상태 및 참조 초기화

먼저, 맵 참조와 상태를 초기화합니다.

const mapRef = useRef(null);
const [zoomHistory, setZoomHistory] = useState([
  { center: koreaCenter, zoom: initialZoom },
]);
const [userLocation, setUserLocation] = useState(null);
  • mapRef는 구글 맵 객체를 참조하기 위한 훅입니다.
  • zoomHistory는 사용자가 변경한 줌 상태를 기록합니다.
  • userLocation은 사용자의 현재 위치를 저장합니다.

줌 초기화 함수

const onClickZoomResetBtn = () => {
  if (mapRef.current) {
    mapRef.current.setCenter(koreaCenter);
    mapRef.current.setZoom(initialZoom);
    setZoomHistory([{ center: koreaCenter, zoom: initialZoom }]);
    setUserLocation(null);
  }
};
  • 지도 중심을 koreaCenter로 설정하고 줌 레벨을 initialZoom으로 설정합니다.
  • zoomHistory를 초기화하여 맨 처음 상태로 되돌립니다.
  • 사용자 위치를 초기화합니다.

이전 줌 상태로 이동 함수

줌 히스토리를 활용해 이전 줌 상태로 되돌아가는 함수입니다.

const onClickPreviousZoomBtn = () => {
  if (mapRef.current && zoomHistory.length > 1) {
    const newHistory = [...zoomHistory];
    newHistory.pop();

    const { center: prevCenter, zoom: prevZoom } = newHistory[newHistory.length - 1];

    mapRef.current.setCenter(prevCenter);
    mapRef.current.setZoom(prevZoom);

    setZoomHistory(newHistory);
    setUserLocation(null);
  }
};
  • zoomHistory 배열에서 마지막 상태를 제거하여 이전 상태로 되돌아갑니다.
  • 지도 중심과 줌 레벨을 이전 상태로 설정합니다.

현재 위치로 이동 함수

사용자의 현재 위치로 지도를 이동시키는 함수입니다.

const onClickUserCurrentLocation = () => {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      (position) => {
        setUserLocation({
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        });
        mapRef.current.setCenter({
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        });
        mapRef.current.setZoom(15);
      },
      (error) => {
        console.error("Error getting user's location: ", error);
      },
    );
  }
};
  • navigator.geolocation API를 사용하여 사용자의 현재 위치를 가져옵니다.
  • 사용자의 현재 위치를 userLocation 상태에 저장하고, 지도 중심을 해당 위치로 설정하며, 줌 레벨을 15로 설정합니다.

마커 클릭 이벤트 핸들러

마커 클릭 시 지도를 해당 마커 위치로 이동시키는 함수입니다.

const onClickMarker = (marker) => {
  if (mapRef.current) {
    const newHistory = [...zoomHistory];
    newHistory.push({
      center: mapRef.current.getCenter().toJSON(),
      zoom: mapRef.current.getZoom(),
    });
    setZoomHistory(newHistory);
    mapRef.current.setCenter({
      lat: marker.lat,
      lng: marker.lng,
    });
    mapRef.current.setZoom(15);
  }
};
  • 마커 클릭 시 현재 줌 상태를 zoomHistory에 추가하고, 클릭한 마커 위치로 지도 중심과 줌 레벨을 설정합니다.

줌 변경 이벤트 핸들러

줌 변경 시 현재 상태를 기록하는 함수입니다.

const onZoomChanged = () => {
  if (mapRef.current) {
    const newHistory = [...zoomHistory];
    newHistory.push({
      center: mapRef.current.getCenter().toJSON(),
      zoom: mapRef.current.getZoom(),
    });
    setZoomHistory(newHistory);
  }
};
  • 줌 레벨이 변경될 때마다 현재 상태를 zoomHistory에 추가합니다.
profile
즐겁게 개발하자 쥬야호👻
post-custom-banner

0개의 댓글