[NextJS] Naver Map Clustering

2023. 11. 30. 13:43NextJS

준비물 : 네이버 맵 사용을 위한 클라이언트 ID

풀코드는 아래와 같음.

import React, { useEffect, useRef } from 'react'

import { NaverMaps, ClusteringMapProps } from '../types/naverMaps'

import authConfig from 'src/configs/auth'

// shopList (상점 목록)과 onShopSelect (상점 선택시 호출할 함수) 두 개의 prop을 받음 
const NaverMapMarketClustering = ({ shopList, onShopSelect }: ClusteringMapProps) => {
    // useRef를 사용하여 지도를 표시할 DOM 요소의 참조(mapRef)를 생성
	const mapRef = useRef(null)

    // 동적으로 외부 스크립트를 로드하는 loadScript 함수를 정의
    // 스크립트 URL(src)과 로드 완료시 호출할 콜백 함수(callback)를 매개변수로 받음
	const loadScript = (src: any, callback: any) => {
        // 이미 로드된 스크립트인지 확인하여 중복 로드를 방지
		if (document.querySelector(`script[src="${src}"]`)) {
			callback()
			return
		}

		const script = document.createElement('script')
		script.src = src
		script.async = true
		script.onload = () => {
			if (callback) callback()
		}
		script.onerror = () => {
			console.error(`Script load error for ${src}`)
		}
		document.head.appendChild(script)
	}

    // 스크립트 로드가 완료되면 지도를 초기화하고 상점 마커를 추가
	const initializeMapAndMarkers = async (navermaps: NaverMaps) => {
		let map: any

        // 네이버 지도 인스턴스를 생성하고, shopList를 이용하여 각 상점 위치에 마커를 추가
		if (mapRef.current) {
			map = new navermaps.Map(mapRef.current, {
				center: new navermaps.LatLng(36, 128),
				zoom: 7,
				mapTypeControl: false,
				mapDataControl: false,
				logoControl: false,
				scaleControl: false,
				zoomControl: true,
				zoomControlOptions: {
					position: navermaps.Position.TOP_RIGHT,
					style: navermaps.ZoomControlStyle.SMALL
				}
			})
		}

		if (shopList) {
			const markers = shopList.map((shop: any) => {
				const position = new navermaps.LatLng(shop.si_branch_latiitude, shop.si_branch_longitude)

				const imageUrl = `${authConfig.imageEndpoint}${shop.si_company_image_logo}`

				const marker = new navermaps.Marker({
					position,
					map,
					title: shop.si_shop_name,
					icon: {
						content: `<img src="${imageUrl}" alt="${shop.si_shop_name}" style="width: 40px; height: 40px; object-fit: contain;">`,
						size: new navermaps.Size(40, 40),
						anchor: new navermaps.Point(20, 20)
					}
				})

                // 마커 클릭 이벤트에 displayShopInfo 함수를 연결하여 상점 정보를 표시
				navermaps.Event.addListener(marker, 'click', () => {
					displayShopInfo(shop)
				})

				return marker
			})

			function displayShopInfo(shop: any) {
				onShopSelect({
					shopName: shop.si_company_name,
					shopAddress: shop.si_company_address_1 + ' ' + shop.si_company_address_2,
					image: shop.si_company_image_main_1
				})
			}

			var htmlMarker1 = {
					content: `<div style="cursor:pointer;width:40px;height:40px;line-height:42px;font-size:10px;color:white;text-align:center;font-weight:bold;background:url(${authConfig.frontEndpoint}/images/image/cluster-marker-1.png);background-size:contain;"></div>`,
					size: new navermaps.Size(40, 40),
					anchor: new navermaps.Point(20, 20)
				},
				htmlMarker2 = {
					content: `<div style="cursor:pointer;width:40px;height:40px;line-height:42px;font-size:10px;color:white;text-align:center;font-weight:bold;background:url(${authConfig.frontEndpoint}/images/image/cluster-marker-2.png);background-size:contain;"></div>`,
					size: new navermaps.Size(40, 40),
					anchor: new navermaps.Point(20, 20)
				},
				htmlMarker3 = {
					content: `<div style="cursor:pointer;width:40px;height:40px;line-height:42px;font-size:10px;color:white;text-align:center;font-weight:bold;background:url(${authConfig.frontEndpoint}/images/image/cluster-marker-1.png);background-size:contain;"></div>`,
					size: new navermaps.Size(40, 40),
					anchor: new navermaps.Point(20, 20)
				},
				htmlMarker4 = {
					content: `<div style="cursor:pointer;width:40px;height:40px;line-height:42px;font-size:10px;color:white;text-align:center;font-weight:bold;background:url(${authConfig.frontEndpoint}/images/image/cluster-marker-2.png);background-size:contain;"></div>`,
					size: new navermaps.Size(40, 40),
					anchor: new navermaps.Point(20, 20)
				},
				htmlMarker5 = {
					content: `<div style="cursor:pointer;width:40px;height:40px;line-height:42px;font-size:10px;color:white;text-align:center;font-weight:bold;background:url(${authConfig.frontEndpoint}/images/image/cluster-marker-1.png);background-size:contain;"></div>`,
					size: new navermaps.Size(40, 40),
					anchor: new navermaps.Point(20, 20)
				}

			// @ts-ignore
            // Cannot find name 'MarkerClustering'. Did you mean 'markerClustering'? 에러 회피
			const markerClustering = new MarkerClustering({
				minClusterSize: 2,
				maxZoom: 8,
				map: map,
				markers: markers,
				disableClickZoom: false,
				gridSize: 120,
				icons: [htmlMarker1, htmlMarker2, htmlMarker3, htmlMarker4, htmlMarker5],
				indexGenerator: [10, 100, 200, 500, 1000],
				stylingFunction: (clusterMarker: any, count: number) => {
					clusterMarker.getElement().querySelector('div:first-child').textContent = count
				}
			})
		}
	}

    // 컴포넌트가 마운트될 때 네이버 지도 API 스크립트와 마커 클러스터링 스크립트를 로드
	useEffect(() => {
		loadScript(
			`https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${authConfig.naverMapCliendId}&submodules=geocoder,panorama`,
			() => {
				if (window.naver && window.naver.maps) {
					loadScript('/scripts/MarkerClustering.js', () => {
						initializeMapAndMarkers(window.naver.maps)
					})
				}
			}
		)
	}, [shopList])

	return <div ref={mapRef} style={{ width: '100%', height: '400px', marginBottom: 15 }} />
}

export default NaverMapMarketClustering

참조 문서 : https://navermaps.github.io/maps.js.ncp/docs/tutorial-marker-cluster.example.html

 

NAVER Maps API v3

NAVER Maps API v3로 여러분의 지도를 만들어 보세요. 유용한 기술문서와 다양한 예제 코드를 제공합니다.

navermaps.github.io

github : https://github.com/navermaps/marker-tools.js/tree/master/marker-clustering

'NextJS' 카테고리의 다른 글

[NextJS] Google map 사용  (1) 2023.11.09
[NextJS] dynamic  (0) 2023.08.03
[NextJS] 빌드 디렉토리 구조  (0) 2023.08.01
[NextJS] getInitialProps 사용법  (0) 2022.08.30