🤔 글을 작성하게 된 이유

출발지와 도착지 마커를 모두 지도 안에 표시하는 것은 사용자 경험을 개선하기 위한 중요한 요소입니다. 마커 사이의 거리가 매번 변할 수 있기 때문에 상황에 맞는 줌 레벨을 설정하고 지도의 범위를 자동으로 조절하는 방법이 필요합니다. 이 글에서는 제가 접근한 다양한 방식을 코드와 함께 소개하고, 각 방법의 장단점을 정리해보겠습니다.

1. 두 마커 사이의 거리 기반 줌 레벨 설정

두 마커 사이의 거리를 계산하여 지도 줌 레벨을 설정하는 방법입니다. 이 방법은 두 위치 간의 거리에 따라 적절한 줌 레벨을 설정하여 사용자가 두 마커를 쉽게 볼 수 있도록 합니다.

// 두 지점 사이의 거리를 측정
export const getDistance = (lat1, lng1, lat2, lng2) => {
  function deg2rad(deg) {
    return deg * (Math.PI / 180);
  }
  const R = 6371; // 지구 반지름 (km)
  const dLat = deg2rad(lat2 - lat1);
  const dLon = deg2rad(lng2 - lng1);
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(deg2rad(lat1)) *
    Math.cos(deg2rad(lat2)) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c; // 거리 (km)
};

import React, { useEffect } from "react";
import { getDistance } from "./getDistance";

const Map = () => {
  const lat1 = 37.49813943152925;
  const lng1 = 127.02827170358093;
  const lat2 = 37.48390185679575;
  const lng2 = 127.03450754512691;

  const step = [100, 50, 30, 20, 10, 5, 3, 1, 0.5, 0.3, 0.1, 0.05, 0.03, 0.02, 0.01, 0.005];
  const distance = getDistance(lat1, lng1, lat2, lng2);
  let zoomLevel = 0;
  for (let i = 0; i < step.length; i++) {
    if (distance > step[i]) {
      zoomLevel = i;
      break;
    }
  }

  useEffect(() => {
    const script = document.createElement("script");
    script.src = `https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=YOUR_APP_KEY`;
    script.async = true;

    script.onload = () => {
      if (window.naver && window.naver.maps) {
        const mapOptions = {
          center: new window.naver.maps.LatLng(lat1, lng1),
          zoom: zoomLevel,
          logoControl: false,
          mapDataControl: false,
          scaleControl: true,
          tileDuration: 200,
        };
        new window.naver.maps.Map("map", mapOptions);
      }
    };

    document.head.appendChild(script);
    return () => {
      document.head.removeChild(script);
    };
  }, [zoomLevel]);

  return <div id="map" style={{ width: "500px", height: "500px" }}></div>;
};

export default Map;

장단점

2. 위도 및 경도의 최대/최소값을 이용한 설정

출발지와 도착지의 위도와 경도의 최솟값과 최댓값을 계산하여 지도를 맞추는 방법입니다. 이 방법은 지도의 범위를 정확히 설정하여 두 마커가 모두 보이도록 보장합니다.

코드 예시

import React, { useEffect } from "react";
import { getDistance } from "./getDistance";

const calculateZoomLevel = (distance, step) => {
  for (let i = 0; i < step.length; i++) {
    if (distance > step[i]) {
      return i;
    }
  }
  return step.length - 1;
};

const getZoomLevelForBounds = (latDiff, lngDiff, step) => {
  const latDistance = getDistance(0, 0, latDiff, 0);
  const lngDistance = getDistance(0, 0, 0, lngDiff);
  const maxDistance = Math.max(latDistance, lngDistance);
  return calculateZoomLevel(maxDistance, step);
};

const Map = ({ lat1, lng1, lat2, lng2 }) => {
  const step = [
    100, 50, 30, 20, 10, 5, 3, 1, 0.5, 0.3, 0.1, 0.05, 0.03, 0.02, 0.01, 0.005,
  ];

  const latDiff = Math.abs(lat1 - lat2);
  const lngDiff = Math.abs(lng1 - lng2);
  const zoomLevel = getZoomLevelForBounds(latDiff, lngDiff, step);

  useEffect(() => {
    const script = document.createElement("script");
    script.src = `https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=YOUR_APP_KEY`;
    script.async = true;

    script.onload = () => {
      if (window.naver && window.naver.maps) {
        const mapOptions = {
          center: new window.naver.maps.LatLng(lat1, lng1),
          zoom: zoomLevel + 7,
          logoControl: false,
          mapDataControl: false,
          scaleControl: true,
          tileDuration: 200,
        };
        new window.naver.maps.Map("map", mapOptions);
      }
    };

    document.head.appendChild(script);
    return () => {
      document.head.removeChild(script);
    };
  }, [lat1, lng1, zoomLevel]);

  return <div id="map" style={{ width: "500px", height: "500px" }}></div>;
};

export default Map;

장단점

단점: 위도와 경도의 최대/최소값만을 기준으로 하기 때문에 마커가 지도의 가장자리에 배치될 수 있으며, 여백이 충분하지 않을 수 있습니다.

  1. LatLngBounds를 사용하여 지도 맞추기

Navermap API의 LatLngBounds 객체를 사용하여 두 마커가 모두 보이도록 지도의 경계를 자동으로 설정하는 방법입니다. 이 방법은 매우 간편하며, 사용자 경험을 고려한 최적의 방법으로 간주됩니다.

코드 예시

import { useEffect } from "react";

const MapWithFitBounds = ({ lat1, lng1, lat2, lng2 }) => {
  useEffect(() => {
    const script = document.createElement("script");
    script.src = `https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=m5sz5l3xvw`;
    script.async = true;

    script.onload = () => {
      if (window.naver && window.naver.maps) {
        const bounds = new window.naver.maps.LatLngBounds(
          new window.naver.maps.LatLng(lat1, lng1),
          new window.naver.maps.LatLng(lat2, lng2)
        );

        const mapOptions = {
          center: bounds.getCenter(),
          zoom: 10,
          logoControl: false,
          mapDataControl: false,
        };
        const map = new window.naver.maps.Map("map", mapOptions);

        map.fitBounds(bounds);
      }
    };

    document.head.appendChild(script);
    return () => {
      document.head.removeChild(script);
    };
  }, [lat1, lng1, lat2, lng2]);

  return <div id="map" style={{ width: "300px", height: "300px" }}></div>;
};

export default MapWithFitBounds;

장단점