WEB GL JS

웹 개발, 어플리케이션에서 활용될 수 있도록 Javascript로 제공되는 지도 플랫폼 입니다.

마커 클러스터


routogl 지도에 표시된 다수의 마커를 클러스터로 표시하는 예제입니다.

지도를 축소하면 클러스터 마커로 표시 됩니다.

const map = new routogl.Map({
  container: 'map', // container ID
  style: routogl.RoutoStyle.LIGHT,
  center: [127.0586339, 37.507009], // 초기 위치 [lng, lat]
  zoom: 15, // 초기 줌 레벨
});

// 지도 생성 완료 후 동작
map.on('load', () => {
  // 랜덤 포인트 생성
  const createRandomPointFeatures = (count) => {
    const result = [];
    for(let i = 0 ; i < count ; i++){
      const feature = {
        'type': 'Feature',
        'properties': {
          "id": Math.floor(Math.random() * 10 ** 10).toString().padStart(10, '0'),
          "mag": Number((Math.random() * 10).toFixed(1)),
          "time": 0,
          "felt": null,
          "tsunami": Math.round(Math.random())
        },
        'geometry': {
          'type': 'Point',
          'coordinates': [
            Number((Math.random() * (127.08 - 127.05) + 127.05).toFixed(7)),
            Number((Math.random() * (37.52 - 37.49) + 37.49).toFixed(6)),
          ]
        }
      };
      result.push(feature);
    }
    return result;
  };

  // 다수의 포인트 예제
  map.addSource('earthquakes', {
    type: 'geojson',
    data: {
      "type": "FeatureCollection",
      "crs": {
        "type": "name",
        "properties": {
          "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
        }
      },
      "features": createRandomPointFeatures(1000)
    },
    cluster: true,
    clusterMaxZoom: 15, // 클러스터 마커가 표시 되는 줌 레벨
    clusterRadius: 10 // 설정한 갯수가 모여있을 때 클러스터 마커로 표시 (기본값: 50)
  });

  // 클러스터 레이어 생성
  map.addLayer({
    id: 'clusters', // 레이어 ID
    type: 'circle', // 레이어 타입
    source: 'earthquakes', // 소스 ID
    filter: ['has', 'point_count'], // 'point_count' 속성 값이 존재하는 Source만 필터링
    paint: {
      'circle-color': [ // 포인트 갯수에 따른 원 색상 설정
        'step',
        ['get', 'point_count'],
        '#51bbd6',
        3,
        '#f1f075',
        6,
        '#f28cb1'
      ],
      'circle-radius': [ // 포인트 갯수에 따른 원 크기 설정
        'step',
        ['get', 'point_count'],
        10,
        2,
        15,
        5,
        20
      ]
    }
  });

  // 클러스터 포인트 갯수 레이어 생성
  map.addLayer({
    id: 'cluster-count', // 레이어 ID
    type: 'symbol', // 레이어 타입
    source: 'earthquakes', // 소스 ID
    filter: ['has', 'point_count'], // 'point_count' 속성 값이 존재하는 Source만 필터링
    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', // 레이어 ID
    type: 'circle', // 레이어 타입
    source: 'earthquakes', // 소스 ID
    filter: ['!', ['has', 'point_count']], // 'point_count' 속성 값이 존재하지 않는 Source만 필터링
    paint: {
      'circle-color': '#11b4da',
      'circle-radius': 4,
      'circle-stroke-width': 1,
      'circle-stroke-color': '#fff'
    }
  });

  // 클러스터 마커 선택시 해당 위치로 지도 이동
  map.on('click', 'clusters', (e) => {
    const features = map.queryRenderedFeatures(e.point, {
      layers: ['clusters']
    });
    const clusterId = features[0].properties.cluster_id;
    map.getSource('earthquakes').getClusterExpansionZoom(
      clusterId,
      (err, zoom) => {
        if (err) return;

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

  // 클러스터 대상이 아닌 포인트 선택시 속성 값을 팝업으로 표시
  map.on('click', 'unclustered-point', (e) => {
    const coordinates = e.features[0].geometry.coordinates.slice();
    const mag = e.features[0].properties.mag;
    const tsunami =
      e.features[0].properties.tsunami === 1 ? 'yes' : 'no';

    if (['mercator', 'equirectangular'].includes(map.getProjection().name)) {
      while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
        coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
      }
    }

    new mapboxgl.Popup()
      .setLngLat(coordinates)
      .setHTML(
        `진도: ${mag}<br>쓰나미 발생 여부: ${tsunami}`
      )
      .addTo(map);
  });

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