마커 클러스터
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 = '';
});
});
