Naver Map API 오버레이 성능 문제

HOSEON YOO·2023년 12월 12일
2

개요

  • Naver Map API v3

Naver Map API를 활용하여 4천 개의 식당 위치를 지도위에 마커로 표시하였다. 그런데 브라우저가 버벅거리는 현상이 발생하였다.. 도저히 지도를 이용하기 어려울 정도의 속도였다.

코드 구현

const response = await fetch('/api/restaurants');
const restaurants = await response.json();

for (const restaurant of restaurants) {
  const lat = restaurant.lat;
  const lng = restaurant.lng;
  const marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(lat, lng),
    map: map
  });
}

현재 화면에 보이는 오버레이만 표시하기

Naver Map API v3 공식문서를 확인해보면 아래와 같이 나와있다.

지도 위에 오버레이가 많이 올라갈수록 브라우저의 그래픽 리소스를 많이 사용하게 되고 그만큼 성능이 느려질 수 밖에 없습니다. 따라서 가능한 한 적은 수의 오버레이만 지도 위에 올리는 것이 성능에 도움이 됩니다.

코드 구현

const response = await fetch('/api/restaurants');
const restaurants = await response.json();
const markers = [];

for (const restaurant of restaurants) {
  const lat = restaurant.lat;
  const lng = restaurant.lng;
  const marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(lat, lng),
  });
  
  markers.push(marker);
}

naver.maps.Event.addListener(map, 'zoom_changed', () => {
  updateMarkers(map, markers);
});

naver.maps.Event.addListener(map, 'dragend', () => {
  updateMarkers(map, markers);
});

function updateMarkers(map, markers) {
  const mapBounds = map.getBounds();

  for (const marker of markers) {
    const position = marker.getPosition();

    if (mapBounds.hasLatLng(position)) {
      showMarker(map, marker);
    } else {
      hideMarker(marker);
    }
  }
}

function showMarker(map, marker) {
  marker.setMap(map);
}

function hideMarker(marker) {
  marker.setMap(null);
}

어느 정도 최적화가 되었지만, 지도를 축소할수록 마커 수가 많아져서 또다시 성능문제가 발생했다.. 이를 해결하기 위해 클러스터링 기능을 사용해보자.

오버레이 클러스터화하기

Naver Map API v3 공식문서를 확인해보면 아래와 같이 나와있다.

지도 위에 많은 마커가 표시될 때 마커를 그룹화하여 나타냅니다.

먼저 Naver Map API v3 공식문서를 보면 GitHub Repository에서 MarkerClustering.js와 이미지를 다운로드 받아야 된다. 그리고 jQuery 구문을 포함하고 있다곤 하는데... 필자는 jQuery 구문을 JavaScript로 바꿔서 작성했다.

<head>
...
<script type="text/javascript" src="./MarkerClustering.js"></script>
...
</head>
const response = await fetch('/api/restaurants');
const restaurants = await response.json();
const markers = [];

for (const restaurant of restaurants) {
  const lat = restaurant.lat;
  const lng = restaurant.lng;
  const marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(lat, lng),
  });
  
  markers.push(marker);
}

const 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('./img/cluster-marker-1.png');background-size:contain;"></div>`,
  size: N.Size(40, 40),
  anchor: N.Point(20, 20),
};
const 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('./img/cluster-marker-2.png');background-size:contain;"></div>`,
  size: N.Size(40, 40),
  anchor: N.Point(20, 20),
};
const 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('./img/cluster-marker-3.png');background-size:contain;"></div>`,
  size: N.Size(40, 40),
  anchor: N.Point(20, 20),
};
const 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('./img/cluster-marker-4.png');background-size:contain;"></div>`,
  size: N.Size(40, 40),
  anchor: N.Point(20, 20),
};
const 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('./img/cluster-marker-5.png');background-size:contain;"></div>`,
  size: N.Size(40, 40),
  anchor: N.Point(20, 20),
};

const markerClustering = new MarkerClustering({
  minClusterSize: 2,
  maxZoom: 10,
  map: map,
  markers: markers,
  disableClickZoom: false,
  gridSize: 120,
  icons: [
    htmlMarker1,
    htmlMarker2,
    htmlMarker3,
    htmlMarker4,
    htmlMarker5,
  ],
  indexGenerator: [10, 100, 200, 500, 1000],
  stylingFunction: (clusterMarker, count) => {
    clusterMarker
      .getElement()
      .querySelector('div:first-child').innerText = count;
  },
});

참고자료

profile
안녕하세요~ 👋, 대한민국 개발자 유호선입니다.

0개의 댓글