Tổng quan
Hướng dẫn này sẽ chỉ cho bạn cách sử dụng phân cụm điểm đánh dấu để hiển thị một số lượng lớn điểm đánh dấu trên bản đồ. Bạn có thể sử dụng thư viện @googlemaps/markerclusterer kết hợp với Maps JavaScript API để kết hợp các điểm đánh dấu ở gần nhau thành cụm và đơn giản hoá việc hiển thị điểm đánh dấu trên bản đồ.
Để xem cách hoạt động của tính năng phân cụm điểm đánh dấu, hãy xem bản đồ sau.
Số trên một cụm cho biết số lượng điểm đánh dấu mà cụm đó chứa. Xin lưu ý rằng khi bạn phóng to bất kỳ vị trí cụm nào, số trên cụm sẽ giảm và bạn bắt đầu thấy các điểm đánh dấu riêng lẻ trên bản đồ. Việc thu nhỏ bản đồ sẽ hợp nhất các điểm đánh dấu thành cụm một lần nữa.
Mẫu sau đây cho thấy mã hoàn chỉnh mà bạn cần để tạo bản đồ này.
TypeScript
import { MarkerClusterer } from '@googlemaps/markerclusterer'; async function init() { // Request needed libraries. const [{ InfoWindow }, { AdvancedMarkerElement, PinElement }] = await Promise.all([ google.maps.importLibrary('maps'), google.maps.importLibrary('marker'), ]); const mapElement = document.querySelector<google.maps.MapElement>('gmp-map')!; const innerMap = mapElement.innerMap; const infoWindow = new InfoWindow({ content: '', disableAutoPan: true, }); // Create an array of alphabetical characters used to label the markers. const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // Add some markers to the map. const markers = locations.map((position, i) => { const label = labels[i % labels.length]; const pinGlyph = new PinElement({ glyphText: label, glyphColor: 'white', }); const marker = new AdvancedMarkerElement({ position, content: pinGlyph, gmpClickable: true, }); // markers can only be keyboard focusable when they have click listeners // open info window when marker is clicked marker.addEventListener('gmp-click', () => { const content = document.createElement('div'); content.textContent = `${String(position.lat)}, ${String(position.lng)}`; infoWindow.setContent(content); infoWindow.open({ anchor: marker, map: innerMap, }); }); return marker; }); // Add a marker clusterer to manage the markers. new MarkerClusterer({ markers, map: innerMap }); } const locations = [ { lat: -31.56391, lng: 147.154312 }, { lat: -33.718234, lng: 150.363181 }, { lat: -33.727111, lng: 150.371124 }, { lat: -33.848588, lng: 151.209834 }, { lat: -33.851702, lng: 151.216968 }, { lat: -34.671264, lng: 150.863657 }, { lat: -35.304724, lng: 148.662905 }, { lat: -36.817685, lng: 175.699196 }, { lat: -36.828611, lng: 175.790222 }, { lat: -37.75, lng: 145.116667 }, { lat: -37.759859, lng: 145.128708 }, { lat: -37.765015, lng: 145.133858 }, { lat: -37.770104, lng: 145.143299 }, { lat: -37.7737, lng: 145.145187 }, { lat: -37.774785, lng: 145.137978 }, { lat: -37.819616, lng: 144.968119 }, { lat: -38.330766, lng: 144.695692 }, { lat: -39.927193, lng: 175.053218 }, { lat: -41.330162, lng: 174.865694 }, { lat: -42.734358, lng: 147.439506 }, { lat: -42.734358, lng: 147.501315 }, { lat: -42.735258, lng: 147.438 }, { lat: -43.999792, lng: 170.463352 }, ]; void init();
JavaScript
import { MarkerClusterer } from '@googlemaps/markerclusterer'; async function init() { // Request needed libraries. const [{ InfoWindow }, { AdvancedMarkerElement, PinElement }] = await Promise.all([ google.maps.importLibrary('maps'), google.maps.importLibrary('marker'), ]); const mapElement = document.querySelector('gmp-map'); const innerMap = mapElement.innerMap; const infoWindow = new InfoWindow({ content: '', disableAutoPan: true, }); // Create an array of alphabetical characters used to label the markers. const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // Add some markers to the map. const markers = locations.map((position, i) => { const label = labels[i % labels.length]; const pinGlyph = new PinElement({ glyphText: label, glyphColor: 'white', }); const marker = new AdvancedMarkerElement({ position, content: pinGlyph, gmpClickable: true, }); // markers can only be keyboard focusable when they have click listeners // open info window when marker is clicked marker.addEventListener('gmp-click', () => { const content = document.createElement('div'); content.textContent = `${String(position.lat)}, ${String(position.lng)}`; infoWindow.setContent(content); infoWindow.open({ anchor: marker, map: innerMap, }); }); return marker; }); // Add a marker clusterer to manage the markers. new MarkerClusterer({ markers, map: innerMap }); } const locations = [ { lat: -31.56391, lng: 147.154312 }, { lat: -33.718234, lng: 150.363181 }, { lat: -33.727111, lng: 150.371124 }, { lat: -33.848588, lng: 151.209834 }, { lat: -33.851702, lng: 151.216968 }, { lat: -34.671264, lng: 150.863657 }, { lat: -35.304724, lng: 148.662905 }, { lat: -36.817685, lng: 175.699196 }, { lat: -36.828611, lng: 175.790222 }, { lat: -37.75, lng: 145.116667 }, { lat: -37.759859, lng: 145.128708 }, { lat: -37.765015, lng: 145.133858 }, { lat: -37.770104, lng: 145.143299 }, { lat: -37.7737, lng: 145.145187 }, { lat: -37.774785, lng: 145.137978 }, { lat: -37.819616, lng: 144.968119 }, { lat: -38.330766, lng: 144.695692 }, { lat: -39.927193, lng: 175.053218 }, { lat: -41.330162, lng: 174.865694 }, { lat: -42.734358, lng: 147.439506 }, { lat: -42.734358, lng: 147.501315 }, { lat: -42.735258, lng: 147.438 }, { lat: -43.999792, lng: 170.463352 }, ]; void init();
CSS
html, body { height: 100%; margin: 0; padding: 0; }
HTML
<html>
<head>
<title>Marker Clustering</title>
<link rel="stylesheet" type="text/css" href="./style.css" />
<script type="module" src="./index.js"></script>
<script>
// prettier-ignore
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
key: "GOOGLE_MAPS_API_KEY",
});
</script>
</head>
<body>
<gmp-map
center="-28.024,140.887"
zoom="3"
map-id="DEMO_MAP_ID"></gmp-map>
</body>
</html>Để minh hoạ, hướng dẫn này sẽ thêm một nhóm điểm đánh dấu vào bản đồ bằng cách sử dụng mảng locations. Bạn có thể sử dụng các nguồn khác để lấy điểm đánh dấu cho bản đồ của mình.
Để biết thêm thông tin, hãy đọc hướng dẫn tạo
điểm đánh dấu.
Thêm trình tạo cụm điểm đánh dấu
Hãy làm theo các bước sau để thêm phân cụm điểm đánh dấu:
- Thêm thư viện phân cụm điểm đánh dấu vào trang hoặc ứng dụng của bạn. Thư viện này có trên NPM tại
@googlemaps/markerclusterer
và trong
kho lưu trữ trên GitHub.
Chọn một trong các tuỳ chọn sau để cài đặt thư viện:
NPM
Cài đặt thư viện @googlemaps/markerclusterer bằng NPM.
npm install @googlemaps/markerclusterer
CDN
Tập lệnh sau đây tải thư viện từ CDN unpkg.com.
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
- Thêm trình phân cụm điểm đánh dấu vào ứng dụng của bạn.
Mã sau đây sẽ thêm trình tạo cụm điểm đánh dấu vào bản đồ. Chọn một trong các tuỳ chọn sau:
NPM
import { MarkerClusterer } from "@googlemaps/markerclusterer"; const markerCluster = new MarkerClusterer({ markers, map });
CDN
Khi được truy cập bằng CDN, thư viện này sẽ có trong biến toàn cục
markerClusterer.const markerCluster = new markerClusterer.MarkerClusterer({ markers, map });
Mẫu này chuyển mảng
markersđếnMarkerClusterer. - (Không bắt buộc) Tuỳ chỉnh phân cụm điểm đánh dấu để thay đổi giao diện biểu tượng cụm, thuật toán phân cụm hoặc hành vi nhấp.
Tuỳ chỉnh phân cụm điểm đánh dấu
Bạn có thể tuỳ chỉnh MarkerClusterer bằng cách chuyển các tuỳ chọn bổ sung trong
MarkerClustererOptions
đối tượng khi tạo thực thể trình tạo cụm:
renderer: Kiểm soát cách điểm đánh dấu cụm được kết xuất trên bản đồ.algorithmvàalgorithmOptions: Kiểm soát cách các điểm đánh dấu được nhóm thành cụm dựa trên khoảng cách và mức thu phóng.onClusterClick: Ghi đè hành vi mặc định khi người dùng nhấp vào điểm đánh dấu cụm.
Bản đồ sau đây cho thấy các điểm đánh dấu có biểu tượng cụm tuỳ chỉnh.
Tuỳ chỉnh biểu tượng cụm
Theo mặc định, MarkerClusterer sử dụng
DefaultRenderer
để tạo biểu tượng cụm SVG. Để tuỳ chỉnh giao diện của điểm đánh dấu cụm, hãy chuyển một đối tượng triển khai giao diện
Renderer
đến tuỳ chọn renderer.
Giao diện Renderer yêu cầu phương thức render(cluster, stats, map) trả về
AdvancedMarkerElement (hoặc google.maps.Marker cũ). Phương thức render nhận các đối số sau:
cluster(Cluster): Biểu thị nhóm điểm đánh dấu, bao gồm:count: Số điểm đánh dấu hiển thị trong cụm.position: Vị trí trung tâmgoogle.maps.LatLngcủa cụm.bounds:google.maps.LatLngBoundscủa cụm.markers: Mảng điểm đánh dấu có trong cụm.
stats(ClusterStats): Cung cấp số liệu thống kê trên tất cả các cụm trong chu kỳ kết xuất hiện tại, chẳng hạn nhưstats.clusters.markers.mean,min,max, vàsum. Bạn có thể sử dụng các giá trị này để tạo kiểu cho cụm một cách linh động dựa trên kích thước tương đối của chúng.map(google.maps.Map): Thực thể bản đồ.
Ví dụ sau đây xác định một renderer tuỳ chỉnh tạo huy hiệu HTML hình tròn cho mỗi
AdvancedMarkerElement và thay đổi màu nền khi số lượng điểm đánh dấu cụm vượt quá kích thước cụm trung bình.
TypeScript
// Customize the cluster icons using a custom Renderer const renderer = { render: ( { count, position, }: { count: number; position: google.maps.LatLng }, stats: ClusterStats ) => { // Change color based on the number of markers in the cluster relative to the mean const isLargeCluster = count > Math.max(10, stats.clusters.markers.mean); const clusterClass = isLargeCluster ? 'large-cluster' : 'small-cluster'; // Build the HTML content for the Advanced Marker const content = document.createElement('div'); content.classList.add('custom-cluster-icon', clusterClass); content.textContent = String(count); return new AdvancedMarkerElement({ position, content, // Adjust zIndex to be above other markers zIndex: 1000 + count, }); }, }; // Add a marker clusterer to manage the markers. new MarkerClusterer({ markers, map: innerMap, renderer }); }
JavaScript
// Customize the cluster icons using a custom Renderer const renderer = { render: ({ count, position }, stats) => { // Change color based on the number of markers in the cluster relative to the mean const isLargeCluster = count > Math.max(10, stats.clusters.markers.mean); const clusterClass = isLargeCluster ? 'large-cluster' : 'small-cluster'; // Build the HTML content for the Advanced Marker const content = document.createElement('div'); content.classList.add('custom-cluster-icon', clusterClass); content.textContent = String(count); return new AdvancedMarkerElement({ position, content, // Adjust zIndex to be above other markers zIndex: 1000 + count, }); }, }; // Add a marker clusterer to manage the markers. new MarkerClusterer({ markers, map: innerMap, renderer }); }
Xem ví dụ hoàn chỉnh về hiển thị tùy chỉnh
TypeScript
import { MarkerClusterer, ClusterStats } from '@googlemaps/markerclusterer'; async function init() { // Request needed libraries. const [{ InfoWindow }, { AdvancedMarkerElement, PinElement }] = await Promise.all([ google.maps.importLibrary('maps'), google.maps.importLibrary('marker'), ]); await customElements.whenDefined('gmp-map'); const mapElement = document.querySelector<google.maps.MapElement>('gmp-map')!; const innerMap = mapElement.innerMap; const infoWindow = new InfoWindow({ content: '', disableAutoPan: true, }); // Create an array of alphabetical characters used to label the markers. const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // Add some markers to the map. const markers = locations.map((position, i) => { const label = labels[i % labels.length]; const pinGlyph = new PinElement({ glyphText: label, glyphColor: 'white', }); const marker = new AdvancedMarkerElement({ position, content: pinGlyph, gmpClickable: true, }); marker.addEventListener('gmp-click', () => { const content = document.createElement('div'); content.textContent = `${String(position.lat)}, ${String(position.lng)}`; infoWindow.setContent(content); infoWindow.open({ anchor: marker, map: innerMap, }); }); return marker; }); // Customize the cluster icons using a custom Renderer const renderer = { render: ( { count, position, }: { count: number; position: google.maps.LatLng }, stats: ClusterStats ) => { // Change color based on the number of markers in the cluster relative to the mean const isLargeCluster = count > Math.max(10, stats.clusters.markers.mean); const clusterClass = isLargeCluster ? 'large-cluster' : 'small-cluster'; // Build the HTML content for the Advanced Marker const content = document.createElement('div'); content.classList.add('custom-cluster-icon', clusterClass); content.textContent = String(count); return new AdvancedMarkerElement({ position, content, // Adjust zIndex to be above other markers zIndex: 1000 + count, }); }, }; // Add a marker clusterer to manage the markers. new MarkerClusterer({ markers, map: innerMap, renderer }); } const locations = [ { lat: -31.56391, lng: 147.154312 }, { lat: -33.718234, lng: 150.363181 }, { lat: -33.727111, lng: 150.371124 }, { lat: -33.848588, lng: 151.209834 }, { lat: -33.851702, lng: 151.216968 }, { lat: -34.671264, lng: 150.863657 }, { lat: -35.304724, lng: 148.662905 }, { lat: -36.817685, lng: 175.699196 }, { lat: -36.828611, lng: 175.790222 }, { lat: -37.75, lng: 145.116667 }, { lat: -37.759859, lng: 145.128708 }, { lat: -37.765015, lng: 145.133858 }, { lat: -37.770104, lng: 145.143299 }, { lat: -37.7737, lng: 145.145187 }, { lat: -37.774785, lng: 145.137978 }, { lat: -37.819616, lng: 144.968119 }, { lat: -38.330766, lng: 144.695692 }, { lat: -39.927193, lng: 175.053218 }, { lat: -41.330162, lng: 174.865694 }, { lat: -42.734358, lng: 147.439506 }, { lat: -42.734358, lng: 147.501315 }, { lat: -42.735258, lng: 147.438 }, { lat: -43.999792, lng: 170.463352 }, ]; void init();
JavaScript
import { MarkerClusterer } from '@googlemaps/markerclusterer'; async function init() { // Request needed libraries. const [{ InfoWindow }, { AdvancedMarkerElement, PinElement }] = await Promise.all([ google.maps.importLibrary('maps'), google.maps.importLibrary('marker'), ]); await customElements.whenDefined('gmp-map'); const mapElement = document.querySelector('gmp-map'); const innerMap = mapElement.innerMap; const infoWindow = new InfoWindow({ content: '', disableAutoPan: true, }); // Create an array of alphabetical characters used to label the markers. const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // Add some markers to the map. const markers = locations.map((position, i) => { const label = labels[i % labels.length]; const pinGlyph = new PinElement({ glyphText: label, glyphColor: 'white', }); const marker = new AdvancedMarkerElement({ position, content: pinGlyph, gmpClickable: true, }); marker.addEventListener('gmp-click', () => { const content = document.createElement('div'); content.textContent = `${String(position.lat)}, ${String(position.lng)}`; infoWindow.setContent(content); infoWindow.open({ anchor: marker, map: innerMap, }); }); return marker; }); // Customize the cluster icons using a custom Renderer const renderer = { render: ({ count, position }, stats) => { // Change color based on the number of markers in the cluster relative to the mean const isLargeCluster = count > Math.max(10, stats.clusters.markers.mean); const clusterClass = isLargeCluster ? 'large-cluster' : 'small-cluster'; // Build the HTML content for the Advanced Marker const content = document.createElement('div'); content.classList.add('custom-cluster-icon', clusterClass); content.textContent = String(count); return new AdvancedMarkerElement({ position, content, // Adjust zIndex to be above other markers zIndex: 1000 + count, }); }, }; // Add a marker clusterer to manage the markers. new MarkerClusterer({ markers, map: innerMap, renderer }); } const locations = [ { lat: -31.56391, lng: 147.154312 }, { lat: -33.718234, lng: 150.363181 }, { lat: -33.727111, lng: 150.371124 }, { lat: -33.848588, lng: 151.209834 }, { lat: -33.851702, lng: 151.216968 }, { lat: -34.671264, lng: 150.863657 }, { lat: -35.304724, lng: 148.662905 }, { lat: -36.817685, lng: 175.699196 }, { lat: -36.828611, lng: 175.790222 }, { lat: -37.75, lng: 145.116667 }, { lat: -37.759859, lng: 145.128708 }, { lat: -37.765015, lng: 145.133858 }, { lat: -37.770104, lng: 145.143299 }, { lat: -37.7737, lng: 145.145187 }, { lat: -37.774785, lng: 145.137978 }, { lat: -37.819616, lng: 144.968119 }, { lat: -38.330766, lng: 144.695692 }, { lat: -39.927193, lng: 175.053218 }, { lat: -41.330162, lng: 174.865694 }, { lat: -42.734358, lng: 147.439506 }, { lat: -42.734358, lng: 147.501315 }, { lat: -42.735258, lng: 147.438 }, { lat: -43.999792, lng: 170.463352 }, ]; void init();
CSS
html, body, gmp-map { height: 100%; margin: 0; padding: 0; } .custom-cluster-icon { width: 45px; height: 45px; border-radius: 50%; border: 2px solid white; display: flex; justify-content: center; align-items: center; color: white; font-weight: bold; font-size: 14px; font-family: sans-serif; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); } .large-cluster { background-color: #ff0000; } .small-cluster { background-color: #0000ff; }
HTML
<html>
<head>
<title>marker-clustering-custom</title>
<link rel="stylesheet" type="text/css" href="./style.css" />
<script type="module" src="./index.js"></script>
<script>
// prettier-ignore
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
key: "GOOGLE_MAPS_API_KEY"
});
</script>
</head>
<body>
<gmp-map
center="-28.024, 140.887"
zoom="3"
map-id="DEMO_MAP_ID"></gmp-map>
</body>
</html>Để xem thêm các ví dụ về trình kết xuất, hãy xem bản minh hoạ về cách sử dụng trình kết xuất .
Sửa đổi thuật toán tạo cụm
Theo mặc định, MarkerClusterer sử dụng
SuperClusterAlgorithm,
thuật toán này tạo cụm điểm đánh dấu bằng cách sử dụng cây k-d. Bạn có thể tuỳ chỉnh hành vi tạo cụm bằng cách chuyển một thực thể thuật toán đến tuỳ chọn
algorithm hoặc bằng cách chuyển các tuỳ chọn cho thuật toán mặc định đến algorithmOptions.
Thư viện này bao gồm các thuật toán tích hợp sau:
SuperClusterAlgorithm(mặc định): Tạo cụm điểm địa lý nhanh chóng bằng cách sử dụng cây k-d. Các tuỳ chọn phổ biến bao gồm:maxZoom: Mức thu phóng tối đa mà cụm được tạo (mặc định:16). Ở các mức thu phóng trênmaxZoom, các điểm đánh dấu riêng lẻ sẽ được hiển thị mà không cần tạo cụm.radius: Bán kính cụm tính bằng pixel (mặc định:60).minPoints: Số điểm đánh dấu tối thiểu cần thiết để tạo thành một cụm (mặc định:2).
SuperClusterViewportAlgorithm: Một biến thể được lọc theo khung hiển thị củaSuperClusterAlgorithmchỉ tạo cụm điểm đánh dấu trong khung hiển thị bản đồ hiện tại cộng với phần đệm pixel (viewportPadding, mặc định:60).GridAlgorithm: Thuật toán tạo cụm dựa trên lưới. Các tuỳ chọn phổ biến bao gồmmaxZoom(mặc định:16),gridSize(kích thước lưới tính bằng pixel, mặc định:40), vàmaxDistance(khoảng cách tối đa tính bằng mét giữa tâm cụm và điểm đánh dấu, mặc định:40000).NoopAlgorithm: Kết xuất tất cả các điểm đánh dấu mà không cần tạo cụm.
Ví dụ sau đây định cấu hình SuperClusterAlgorithm với radius và maxZoom tuỳ chỉnh:
import {MarkerClusterer, SuperClusterAlgorithm} from '@googlemaps/markerclusterer'; const markerCluster = new MarkerClusterer({ map, markers, algorithm: new SuperClusterAlgorithm({ radius: 80, maxZoom: 15, }), });
Để so sánh cách hoạt động của các thuật toán khác nhau, hãy xem bản minh hoạ so sánh thuật toán .
Tuỳ chỉnh hành vi nhấp vào cụm
Theo mặc định, việc nhấp vào điểm đánh dấu cụm sẽ khớp khung hiển thị bản đồ với ranh giới của cụm đã nhấp
(map.fitBounds(cluster.bounds)). Để tuỳ chỉnh những gì xảy ra khi người dùng nhấp vào một cụm, hãy truyền một hàm callback đến tuỳ chọn
onClusterClick:
const markerCluster = new MarkerClusterer({ map, markers, onClusterClick: (event, cluster, map) => { // Zoom in by 2 levels centered on the cluster position map.setCenter(cluster.position); map.setZoom((map.getZoom() ?? 3) + 2); }, });
Tìm hiểu thêm
Bạn có thể xem các ví dụ phức tạp hơn về tính năng tạo cụm điểm đánh dấu trong kho lưu trữ trên GitHub và đọc tài liệu tham khảo cho thư viện.