React Map GL 라이브러리의 인터랙션 기능을 직접 구현해보자

BangDori·2023년 12월 23일
0

RooTrip

목록 보기
5/6

이 글은 이전에 작성한 이미지 메타 데이터 추출해서 Mapbox와 상호작용하기와 연결되는 글입니다. 이 글에서는 제가 추출한 좌표를 바탕으로 어떻게 Mapbox 확대 기능을 구현했는지 한 번 알아보겠습니다.

📕 목차

  1. 문제 분석
  2. 추출한 메타 데이터를 200% 활용하기
  3. React Map GL Source를 활용해보자
  4. Mapbox Studio를 활용해서 더 생생한 UX 구현하기
  5. 후기

문제 분석

이전에는 이미지를 업로드하면 아래와 같이 단순히 이미지가 표시되는 것을 확인할 수 있었습니다.

이미지의 메타 데이터를 잘 추출해서 지도에 잘 보여주고 있긴한데,, 한 가지 아쉬운 점이 있었습니다. 바로 대구에서 찍은 사진을 여러 장 찍어 올린다면 한 장으로 겹쳐보인다는 점이였습니다.

하나는 영남대학교 과학도서관에서 찍은 사진이고 하나는 이월드에서 찍은 사진이라 생각보다 거리가 있음에도 불구하고, 지도에서는 마커가 거의 동일 위치상에 존재해서 사용자가 직접 드래그로 이동해서 정확한 위치를 확인해야만 했습니다.

그래서 해당 문제를 분석한 결과 지도의 Zoom이 이미지의 업로드 유무와 관계없이 고정적이라는 것을 알 수 있었고, 이에 따라 사용자가 업로드한 이미지의 좌표에 맞춰 지도의 Zoom이 변경되어야 한다는 것을 파악할 수 있었습니다.

그렇다면, 한번 고생하러 가보겠습니다.

추출한 메타 데이터를 200% 활용하기

이미지의 좌표에 맞춰 지도의 Zoom이 변경되어야 한다는 것은 사용자가 업로드한 이미지에 따라 결정되는 요소이기 때문에 우선 이미지가 업로드 되는 케이스들을 고려해보았습니다.

  1. 이미지 간 거리가 넓을 경우
  2. 이미지 간 거리가 중간일 경우
  3. 이미지 간 거리가 좁을 경우

해당 케이스들은 결국 지도에 표시되어 있는 마커들의 모든 좌표의 합과 범위를 알고 난 이후 결정할 수 있기에 우선 평균을 구하는 로직을 작성하였습니다.

/**
 * 인자로 받은 마커들의 좌표의 합을 구해주는 함수
 * @param {*} markers 지도에 표시되어 있는 마커 객체
 * @returns latitude의 총합, longitude의 총합
 */
export function getTotalToCoordinate(markers) {
  let totalLng = 0;
  let totalLat = 0;

  markers.forEach((marker) => {
    if (marker === null) return;

    const { longitude: lng, latitude: lat } = marker.coordinate;
    totalLng += Number(lng);
    totalLat += Number(lat);
  });

  return { totalLng, totalLat };
}

우선 인자로 markers을 받고 marker를 순회하면서, 모든 longitudelatitude을 추가해주어 모든 좌표의 합을 구해주는 함수입니다.

/**
 * 인자로 받은 마커들의 경도와 위도 범위를 계산해주는 함수
 * @param {*} markers 지도에 표시되어 있는 마커 객체
 * @returns latitude 범위, longitude 범위
 */
export function getRangeToCoordinate(markers) {
  let minLng = Infinity;
  let maxLng = -Infinity;
  let minLat = Infinity;
  let maxLat = -Infinity;

  markers.forEach((marker) => {
    if (marker === null) return;

    const { longitude: lng, latitude: lat } = marker.coordinate;

    minLng = Math.min(minLng, lng);
    maxLng = Math.max(maxLng, lng);
    minLat = Math.min(minLat, lat);
    maxLat = Math.max(maxLat, lat);
  });

  // 경도와 위도 범위 계산
  const lngRange = maxLng - minLng;
  const latRange = maxLat - minLat;

  return { lngRange, latRange };
}

위 함수는 인자로 받은 markers들의 범위를 반환해주는 함수입니다. 이제 모든 좌표의 합과 범위를 구할 수 있게 되었으니 Zoom을 어떻게 결정했는지를 알아보겠습니다.

우선 첫번째, 위와 같이 매우 넓은 범위인 lngRange > 2.5 || latRange > 3.5인 경우에는 zoom을 6으로 설정해서 넓은 범위의 마커들이 한 눈에 들어올 수 있도록 구현하였습니다.

그리고 두번째, 중간 범위인 경우 lngRange > 1 || latRange > 2인 경우에는 Zoom을 7로 설정해서 한 눈에 들어올 수 있게 하였습니다.

그리고 마지막 좁은 범위에서는 Zoom을 9로 설정하였는데, 마커가 표시되는 이름이 도로 끝나거나 광주인 경우 지도가 한 눈에 들어오지 않고 짤리게 되어서, zoom을 8로 달리 설정하였습니다.

/**
 * 지도의 현재 뷰를 변경하는 상호작용 함수
 * @param {Object} markers
 * @returns
 */
export function changeCityToCoordinate(markers) {
  const { totalLng, totalLat } = getTotalToCoordinate(markers);
  const { lngRange, latRange } = getRangeToCoordinate(markers);

  // zoom level 설정
  let zoom = 9;

  if (lngRange > 2.5 || latRange > 3.5) {
    zoom = 6; // 매우 넓은 범위
  } else if (lngRange > 1 || latRange > 2) {
    zoom = 7; // 중간 범위
  } else {
    if (markers[0].name) {
      if (markers[0].name.endsWith('도') || markers[0].name === '광주')
        zoom = 8; // 좁은 범위
      else zoom = 9; // 매우 좁은 범위
    }
  }

  const avgLng = totalLng / markers.length;
  const avgLat = totalLat / markers.length;

  return { center: [avgLng, avgLat], zoom };
}

오우, 이제 이미지를 업로드하니까 지도가 막 알아서 잘 움직이는군요. 물론 내부에는 redux에서 마커 저장하고,, 지도랑 상호작용하고 아주 복잡하게 코드가 짜여져 있지만,, 일단 핵심 로직은 완성이 되었네요! 자 한번 확인해볼까요!

마커도 잘 찍히고, 위치도 잘 이동하는데 어라라 뭔가 좀 이상하네요. 뭔가 지도가 왼쪽으로 조금만,, 더 이동했으면 좋겠는 느낌? 이랄까? 바로 고치러 가보겠습니다.

위 문제점을 해결하기 위해서 centerlongitude를 약간 미세조정 해주겠습니다. 이 부분은 제가 위 3가지의 케이스에 대해서 하나하나 수정한 값을 추가하였습니다.

아래는 완성된 코드입니다.

/**
 * 지도의 현재 뷰를 변경하는 상호작용 함수
 * @param {Object} markers
 * @returns
 */
function changeCityToCoordinate(markers) {
  const { totalLng, totalLat } = getTotalToCoordinate(markers);
  const { lngRange, latRange } = getRangeToCoordinate(markers);

  // zoom level 설정
  let zoom = 9;
  let weight = 0;

  if (lngRange > 2.5 || latRange > 3.5) {
    zoom = 6; // 매우 넓은 범위
    weight = 2.75;
  } else if (lngRange > 1 || latRange > 2) {
    zoom = 7; // 중간 범위
    weight = 1.3;
  } else {
    if (markers[0].name) {
      if (markers[0].name.endsWith('도') || markers[0].name === '광주')
        zoom = 8; // 좁은 범위
      else zoom = 9; // 매우 좁은 범위
    }

    weight = 0.5;
  }

  const avgLng = totalLng / markers.length;
  const avgLat = totalLat / markers.length;

  return { center: [avgLng + weight, avgLat], zoom };
}

가중치를 하나씩 만들어서 미세 조정한 다음에 center에 올 longitude를 조정해준 결과 아래와 같이 이쁘게 변신하게 되었습니다!

짜자자자잔!!!!!!!!! 내가 직접 인터랙티브 기능을 구현했다니... 말도 안돼

다음 시간에는 진짜 마지막으로 React Map GL Source를 활용하여 마커와 마커를 연결하였던 후기에 대해 포스팅하는 시간을 가져보겠습니다.

profile
Happy Day 😊❣️

0개의 댓글