본문 바로가기
개발활용툴

Mapbox 사용법 (2) - 클러스터 사용예제

by 즐거운코딩 2023. 11. 12.
반응형

Mapbox 기본 사용법은 document를 참고하면 되는데 Node.js, express, mongoDB를 이용하여 지도에 데이터를 표출하는 사용하는 예시에 대해 설명하고자 합니다.

물론 Mapbox는 다양한 언어를 지원하기 때문에 각자 사용하는 언어 기준을 참고하면 되고, 맵 사용에 따른 기본적인 설정은 비슷하기 때문에 개발하는데 참고하기 바랍니다.

 

1. 지도 사용 등록

2023.11.10 - [개발활용툴] - Mapbox 사용법(1) 가입 및 기본사용

 

Mapbox 사용법(1) 가입 및 기본사용

위치정보를 지도에 표출하기 위해 구글맵, 네이버, 카카오 등 다양한 맵을 사용할 수 있습니다. 이번에는 Open Street Map 기반의 지도서비스인 Mapbox를 이용하여 지도를 표출하는 방법을 설명하고자

peter-codinglife.tistory.com

이전 블로그를 참고하시고 실제 예제에 맞춰 다음과 같이 작성합니다.

예제의 주용 내용은 다음과 같습니다.

  • 전국의 관광지 정보를 지도에 표출
  • 지도 레벨에 따라 clustering 하여 보여주며 레벨업 할 때 작은 cluster로 변경
  • 개별 point 선택시 세부 내용 팝업 표출

지도표출되는 index.ejs 파일입니다. 

  • map의 id를 cluster-map 으로 지정
  • 지도 표출 사항은 별도 javascript 파일을 생성 (clusterMap.js)
  • 지도 생성에 필요한 mapToken 데이터와 지도표출용 데이터를 <script> tag내 포함
  • 필요정보를 clusterMap.js로 넘겨줌
<% layout('/layouts/boilerplate') %>
<div id="cluster-map"></div>
<div class="mb-3"><a href="/campgrounds/new">관광지 추가</a></div>
<form
  action="/campgrounds/city"
  class="row g-3"
  method="GET"
  novalidate
  class="validated-form"
  enctype="multipart/form-data"
>
  <div class="col-auto">
    <select
      class="form-select form-select-sm mb-3"
      aria-label="Small select example"
      name="city"
    >
      <option selected>검색할 지역을 선택하세요</option>
      <% for (let city of cities) {%>
      <option value="<%= city %>"><%=city %></option>
      <% } %>
    </select>
  </div>
  <div class="col-auto">
    <button type="submit" class="btn btn-primary mb-3">검색</button>
  </div>
</form>
<% for (let campground of campgrounds) { %>
<div class="card mb-3">
  <div class="row">
    <div class="col-md-4">
      <% if(campground.images.length) { %>
      <img
        class="img-fluid"
        src="<%= campground.images[0].url %>"
        alt=""
      />
      <% } else { %>
      <img
        class="img-fluid"
        src="https://res.cloudinary.com/dc2gmdv7u/image/upload/...jpg"
        alt=""
      />
      <% } %>
    </div>
    <div class="col-md-8">
      <div class="card-body">
        <h5 class="card-title"><%= campground.trrsrtNm %></h5>
        <p class="card-text">
          <%= (campground.trrsrtIntrcn.length > 200) ?
          campground.trrsrtIntrcn.slice(0,200)+" ..." : campground.trrsrtIntrcn
          %>
        </p>
        <p class="card-text">
          <small class="text-muted"><%= campground.addr %></small>
        </p>
        <a class="btn btn-primary" href="/campgrounds/<%= campground._id %>"
          >세부정보</a
        >
      </div>
    </div>
  </div>
</div>
<% } %> <% if (startPage > 0) { %>
<nav aria-label="Page navigation example">
  <ul class="pagination justify-content-center">
    <% if (startPage > maxPage) { %>
    <li class="page-item">
      <a
        class="page-link"
        href="/campgrounds/?page=<%= startPage-1 %>"
        aria-label="Previous"
      >
        <span aria-hidden="true">&laquo;</span>
      </a>
    </li>
    <% } else { %>
    <li class="page-item disabled">
      <a class="page-link" href="#" aria-label="Previous">
        <span aria-hidden="true">&laquo;</span>
      </a>
    </li>
    <% } %> <% for ( i = startPage; i <= endPage ;i++) { %>
    <li class="page-item">
      <a class="page-link <%= currentPage === i && "active" %>" href="/campgrounds/?page=<%= i %>"><%=i %></a>
    </li>
    <% } %> <% if (endPage < totalPage ) { %>
    <li class="page-item">
      <a
        class="page-link"
        href="/campgrounds/?page=<%= endPage+1 %>"
        aria-label="Next"
      >
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
    <% } else { %>
    <li class="page-item">
      <a class="page-link disabled" href="#" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
    <% } %>
  </ul>
</nav>
<% } %>
<script>
  const mapToken = "<%- process.env.MAPBOX_TOKEN %>";
  const campgrounds = {features: <%- JSON.stringify(campgroundsAll) %>};
</script>
<script src="/javascripts/clusterMap.js"></script>

 

clusterMap.js 내용은 다음과 같습니다.

  • mapboxgl.Map 으로 기본적인 지도의 스타일(style), 지도 중심점(center), 레벨(zoom)을 지정
  • 지도 조작을 위한 control 버튼 기능을 map.add 로 추가
  • 지도에 표출할 데이터를 map.on 으로 불러오기 - 데이터 타입은 json, cluster사용 최대 레벨 및 반경크기를 지정
  • map.addLayer 에서 cluster 단계, 데이터 수, 단계별 색상을 지정
  • cluster click 시 동작을 지정
  • 각 cluster, point 의 사이즈, 텍스트, 색상을 지정
  • 지도에서 각  데이터 선택시 팝업으로 표출하기
  • 기본적인 지도 지명을 한국어로 변경 하기 (defaultLanguage: "ko")
mapboxgl.accessToken = mapToken;
const map = new mapboxgl.Map({
  container: "cluster-map",
  // Choose from Mapbox's core styles, or make your own style with Mapbox Studio
  style: "mapbox://styles/mapbox/light-v11",
  center: [127.4914411, 36.6358351], // 충청북도청 위치
  zoom: 6,
});

map.addControl(new mapboxgl.NavigationControl());

map.on("load", () => {
  // Add a new source from our GeoJSON data and
  // set the 'cluster' option to true. GL-JS will
  // add the point_count property to your source data.
  map.addSource("campgrounds", {
    type: "geojson",
    // Point to GeoJSON data. This example visualizes all M1.0+ earthquakes
    // from 12/22/15 to 1/21/16 as logged by USGS' Earthquake hazards program.
    data: campgrounds,
    cluster: true,
    clusterMaxZoom: 14, // Max zoom to cluster points on
    clusterRadius: 50, // Radius of each cluster when clustering points (defaults to 50)
  });

  map.addLayer({
    id: "clusters",
    type: "circle",
    source: "campgrounds",
    filter: ["has", "point_count"],
    paint: {
      // Use step expressions (https://docs.mapbox.com/style-spec/reference/expressions/#step)
      // with three steps to implement three types of circles:
      //   * Blue, 20px circles when point count is less than 100
      //   * Yellow, 30px circles when point count is between 100 and 750
      //   * Pink, 40px circles when point count is greater than or equal to 750
      "circle-color": [
        "step",
        ["get", "point_count"],
        "#FF6F91",
        10,
        "#FF9671",
        30,
        "#FFC75F",
      ],
      "circle-radius": ["step", ["get", "point_count"], 15, 10, 20, 30, 25],
    },
  });

  map.addLayer({
    id: "cluster-count",
    type: "symbol",
    source: "campgrounds",
    filter: ["has", "point_count"],
    layout: {
      "text-field": ["get", "point_count_abbreviated"],
      "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
      "text-size": 12,
    },
  });

  map.addLayer({
    id: "unclustered-point",
    type: "circle",
    source: "campgrounds",
    filter: ["!", ["has", "point_count"]],
    paint: {
      "circle-color": "#FF6F91",
      "circle-radius": 4,
      "circle-stroke-width": 1,
      "circle-stroke-color": "#fff",
    },
  });

  // inspect a cluster on click
  map.on("click", "clusters", (e) => {
    const features = map.queryRenderedFeatures(e.point, {
      layers: ["clusters"],
    });
    const clusterId = features[0].properties.cluster_id;
    map
      .getSource("campgrounds")
      .getClusterExpansionZoom(clusterId, (err, zoom) => {
        if (err) return;

        map.easeTo({
          center: features[0].geometry.coordinates,
          zoom: zoom,
        });
      });
  });

  // When a click event occurs on a feature in
  // the unclustered-point layer, open a popup at
  // the location of the feature, with
  // description HTML from its properties.
  map.on("click", "unclustered-point", (e) => {
    const { popUpMarkup } = e.features[0].properties;
    const coordinates = e.features[0].geometry.coordinates.slice();

    // Ensure that if the map is zoomed out such that
    // multiple copies of the feature are visible, the
    // popup appears over the copy being pointed to.
    while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
      coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
    }

    new mapboxgl.Popup().setLngLat(coordinates).setHTML(popUpMarkup).addTo(map);
  });

  map.on("mouseenter", "clusters", () => {
    map.getCanvas().style.cursor = "pointer";
  });
  map.on("mouseleave", "clusters", () => {
    map.getCanvas().style.cursor = "";
  });
});

mapboxgl.setRTLTextPlugin(
  "https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.3/mapbox-gl-rtl-text.js"
);
map.addControl(
  new MapboxLanguage({
    defaultLanguage: "ko",
  })
);

 

전국 지도 클러스터 맵
클러스터 선택으로 확대된 지도

 

데이터 포이트 선택시 팝업표출

 

반응형