Google Maps API와 React를 사용하여 사용자 위치를 지도에 표시하고 클러스터링을 통해 마커를 그룹화하는 방법을 다룹니다. 프로젝트를 설정하고 필요한 라이브러리를 설치한 후, 지도 컴포넌트를 생성하고 사용자 위치를 표시하는 방법을 단계별로 설명하겠습니다.
설치
npm install @react-google-maps/api
Google Cloud Platform에서 프로젝트를 생성하고 Google Maps API를 활성화한 후, API 키를 발급받습니다. 이 키는 .env 파일에 저장하여 환경 변수를 통해 사용합니다.
.env 파일 생성
REACT_APP_GOOGLE_MAPS_API_KEY=YOUR_API_KEY_HERE
✅ 커스텀 마커
✅ 클러스터 설정 및 커스텀
✅ 한반도 기준으로 줌 설정
✅ 지도 이동, 확대/축소 막기
✅ 마커 클릭 시 마커 중심으로 확대
✅ 줌 초기화/ 이전 줌 이동/ 현재 위치로 줌 버튼
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>
);}
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 }, // 송정해수욕장
];
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,
};
};
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>
);}
먼저, 맵 참조와 상태를 초기화합니다.
const mapRef = useRef(null);
const [zoomHistory, setZoomHistory] = useState([
{ center: koreaCenter, zoom: initialZoom },
]);
const [userLocation, setUserLocation] = useState(null);
const onClickZoomResetBtn = () => {
if (mapRef.current) {
mapRef.current.setCenter(koreaCenter);
mapRef.current.setZoom(initialZoom);
setZoomHistory([{ center: koreaCenter, zoom: initialZoom }]);
setUserLocation(null);
}
};
줌 히스토리를 활용해 이전 줌 상태로 되돌아가는 함수입니다.
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);
}
};
사용자의 현재 위치로 지도를 이동시키는 함수입니다.
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);
},
);
}
};
마커 클릭 시 지도를 해당 마커 위치로 이동시키는 함수입니다.
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);
}
};
줌 변경 시 현재 상태를 기록하는 함수입니다.
const onZoomChanged = () => {
if (mapRef.current) {
const newHistory = [...zoomHistory];
newHistory.push({
center: mapRef.current.getCenter().toJSON(),
zoom: mapRef.current.getZoom(),
});
setZoomHistory(newHistory);
}
};