프로젝트 정리/애완동물 종합 솔루션(CatDogForest)

Kakao Map API with 애완동물병원 #7

지난 이야기

 

2023.03.17 - [프로젝트 정리/애완동물 종합 솔루션(CatDogForest)] - Kakao Map API with 애완동물병원 #6

 

원하는 대로 dragend, zoom_changed 이벤트가 있을때 마다

지도의 남서와 북동쪽의 위경도를 기준으로 데이터를 가져오도록 만들었다.

 

map.html

<!DOCTYPE html>
<html lang="ko" itemscope itemtype="http://schema.org/WebPage" xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/default_layout.html}">


<div layout:fragment="content">
  <div class="col-12 d-flex justify-content-center py-2 px-4">
    <div id="map" style="width:100%;height:250px;"></div>
  </div>
  <hr class="border border-primary border-1 opacity-75">
  <div class="col-12 d-flex justify-content-center py-2 px-4">
    <div class="col-4">
      <img class="img border-radius-lg max-width-100 w-100" src="/assets/img/hospital.png" alt="bruce">
    </div>
    <div class="col-8">
      <div class="d-flex justify-content-between align-items-center mb-1 text-sm">
        <h4 class="mb-0">불타는 지옥 병원</h4>
        <div class="d-block">
          <a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill text-primary text-gradient" viewBox="0 0 16 16">
            <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
          </svg></a>
        </div>
      </div>
      <div class="row mb-1 text-xs">
        <div class="col-auto">
          <span class="h6">777</span>
          <span>Posts</span>
        </div>
        <div class="col-auto">
          <span class="h6">7.7Mk</span>
          <span>Followers</span>
        </div>
      </div>
      <p class="text-xs mb-0">
        Decisions: 우리 고양이는 물어요. <br>
        <a href="javascript:;" class="text-info icon-move-right">More about me
          <i class="fas fa-arrow-right text-sm ms-1" aria-hidden="true"></i>
        </a>
      </p>
    </div>
  </div>
  <script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=fd70fd57d8ee6b2f63d1997fc16c9cf5&libraries=services&libraries=clusterer"></script>
  <script>

    // navigator.geolocation logic -> 현재 위.경도를 가져옴 -> 못가져오면 기본 값으로 map을 생성함
    if(navigator.geolocation){
      console.log('지오로케이션 먹음')
      navigator.geolocation.getCurrentPosition((position) => {
        var currentPosition = new kakao.maps.LatLng(position.coords.latitude, position.coords.longitude);
        map.setCenter(currentPosition);
      });
    }else{
      console.log('지오로케이션 안먹음')
    }

    let markers = [];

    let mapContainer = document.getElementById('map'), // 지도를 표시할 div
            mapOption = {
              center: new kakao.maps.LatLng(36.49334, 127.27856), // 지도의 중심좌표
              level: 8, // 지도의 확대 레벨
              mapTypeId : kakao.maps.MapTypeId.ROADMAP // 지도종류
            };

    // 지도를 생성한다
    let map = new kakao.maps.Map(mapContainer, mapOption);

    // 마커 클러스터러를 생성합니다
    let clusterer = new kakao.maps.MarkerClusterer({
      map: map, // 마커들을 클러스터로 관리하고 표시할 지도 객체
      averageCenter: true, // 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
      minLevel: 9 // 클러스터 할 최소 지도 레벨
    });

    // 지도 타입 변경 컨트롤을 생성한다
    const mapTypeControl = new kakao.maps.MapTypeControl();

    // 지도의 상단 우측에 지도 타입 변경 컨트롤을 추가한다
     map.addControl(mapTypeControl, kakao.maps.ControlPosition.TOPRIGHT);

    // 지도에 확대 축소 컨트롤을 생성한다
    const zoomControl = new kakao.maps.ZoomControl();

    // 지도의 우측에 확대 축소 컨트롤을 추가한다
     map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT);

    function getBounds() {
      let center = map.getCenter();
      let bounds = map.getBounds();
      let sw = bounds.getSouthWest();
      let ne = bounds.getNorthEast();
      let swLat = sw.getLat();
      let swLng = sw.getLng();
      let neLat = ne.getLat();
      let neLng = ne.getLng();
      let centerLat = center.getLat();
      let centerLng = center.getLng();
      let position = [swLat, swLng, neLat, neLng, centerLat, centerLng];
      return position;
    }
    function getData() {

      let position = getBounds();
      fetch('/api/user/hospital/map?position='+position)
              .then(response => response.json())
              .then(data => {

                for (let i = 0; i < data.length; i++ ) {
                  // 지도에 마커를 생성하고 표시한다.
                  let marker = new kakao.maps.Marker({
                    position: new kakao.maps.LatLng(data[i].latitude, data[i].longitude), // 마커의 좌표
                    title: data.address_name,
                    map: map // 마커를 표시할 지도 객체
                  });
                  // 인포윈도우를 생성하고 지도에 표시합니다.
                  let infowindow = new kakao.maps.InfoWindow({
                    content : "<div class='d-flex flex-column m-3'><h6 class='text'>"
                            +data[i].place_name+"</h6>" +
                            "<label class='text'>주소 : "
                            +data[i].address_name+"</label>" +
                            "<div className='d-flex'>" +
                            "<a class='btn btn-sm btn-primary' href='"+data[i].place_url+"'>"+
                            "자세히</a>" +
                            "<a class='btn btn-sm btn-secondary ms-1' href=tel:'"+data[i].phone+"'>"+
                            "전화</a>"+
                            "</div></div>",
                    removable: true
                  });

                  // 마커에 이벤트를 등록하는 함수 만들고 즉시 호출하여 클로저를 만듭니다
                  // 클로저를 만들어 주지 않으면 마지막 마커에만 이벤트가 등록됩니다
                  (function(marker, infowindow) {
                    // 마커에 mouseover 이벤트를 등록하고 마우스 오버 시 인포윈도우를 표시합니다
                    kakao.maps.event.addListener(marker, 'click', function() {
                      infowindow.open(map, marker);
                    });

                    // 마커에 mouseout 이벤트를 등록하고 마우스 아웃 시 인포윈도우를 닫습니다
                    /*
                    kakao.maps.event.addListener(marker, 'mouseout', function() {
                      infowindow.close();
                    });
                     */
                  })(marker, infowindow);

                  // 생성된 마커를 마커 저장하는 변수에 넣음
                  markers.push(marker);
                }
                clusterer.addMarkers(markers);
              });
    }

    // 다중 마커 생성
    document.addEventListener('DOMContentLoaded', () =>{
       getData();

    });

    kakao.maps.event.addListener(map, 'zoom_changed', function() {
      deleteClusterMarkers();
      deleteMarkers();
      getData();

    });

    kakao.maps.event.addListener(map, 'dragend', function () {
      deleteClusterMarkers();
      deleteMarkers();
      getData();

    });

    kakao.maps.event.addListener( clusterer, 'clustered', function( clusterer ) {
      clusterer.forEach(cluster => {
        let tempMarkers = cluster.getMarkers();
        temp = tempMarkers[0].address_name;
        console.log(temp);
        //TODO: 클러스터의 마커 정보를 확인 및 적용할 text를 구해서 각 클러스터의 커스텀 오버레이 content에 text를 재설정해주세요.
        cluster.getClusterMarker().getContent().innerText = temp;
      });
    });

    // Deletes all markers in the array by removing references to them.
    function deleteMarkers() {
      for(let idx = 0; idx < markers.length; idx++){
        markers[idx].setMap(null);
      }
      markers = [];
    }

    function deleteClusterMarkers() {
      for(let sidx = 0; sidx < markers.length; sidx++){
        clusterer.removeMarker(markers[sidx]);
      }
      clusterer = new kakao.maps.MarkerClusterer({
        map: map, // 마커들을 클러스터로 관리하고 표시할 지도 객체
        averageCenter: true, // 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
        minLevel: 9 // 클러스터 할 최소 지도 레벨
      });
    }

  </script>
</div>


</html>

 

 

위도와 경도 데이터로 가까운 거리를 계산하는 하버사인 공식을 활용

https://en.wikipedia.org/wiki/Haversine_formula

 

Haversine formula - Wikipedia

From Wikipedia, the free encyclopedia Formula for the great-circle distance between two points on a sphere The haversine formula determines the great-circle distance between two points on a sphere given their longitudes and latitudes. Important in navigati

en.wikipedia.org

 

hospitalMapper.xml

<select id="getAll" resultMap="hospitalMap" parameterType="hashmap">
        SELECT
        *,
        (
        6371 * acos (
        cos ( radians(latitude ) )
        * cos ( radians(#{centerLat} ) )
        * cos ( radians(longitude) - radians(#{centerLng} ) )
        + sin ( radians(latitude))
        * sin ( radians(#{centerLat} ) )
        )
        )
        AS distance
        FROM hospital
        where
        latitude BETWEEN #{swLat} and #{neLat}
        and
        longitude BETWEEN #{swLng} and #{neLng}
        ORDER BY distance
    </select>

 

 

위 영상과 같이 동작하는데 이벤트마다 DB에서 marker와 클러스터를 다시 뿌려주니 동작이 영 느리다.

기본적으로 모든 병원 정보를 가져와서 움직임이 있을때마다 좌표 기준으로 최대 5개 아이템만 하단부에 리스트로 뿌려주도록 동작시키는게 최적화의 방법인듯 싶다.