نمای کلی
این آموزش گامبهگام نحوه استفاده از خوشههای نشانگر را برای نمایش تعداد زیادی نشانگر روی نقشه نشان میدهد. میتوانید از کتابخانه @googlemaps/markerclusterer در ترکیب با Maps JavaScript API برای ترکیب کردن نشانگرهای نزدیک به هم در خوشهها و ساده کردن نمایش نشانگرها در نقشه استفاده کنید.
برای دیدن نحوه عملکرد خوشهبندی نشانگرها، نقشه زیر را مشاهده کنید.
عدد روی خوشه نشان میدهد که چند نشانگر در آن وجود دارد. توجه داشته باشید که با زومپیش کردن روی هریک از مکانهای خوشه، تعداد روی خوشه کاهش مییابد و شروع به دیدن نشانگرهای تکی روی نقشه میکنید. کوچکنمایی نقشه نشانگرها را دوباره در خوشهها ادغام میکند.
نمونه زیر کد کاملی را که برای ایجاد این نقشه نیاز دارید نشان میدهد.
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>بهعنوان نمونه، این آموزش گامبهگام مجموعهای از نشانگرها را بااستفاده از
locations آرایه به نقشه اضافه میکند. میتوانید از منابع دیگر برای دریافت نشانگرهای نقشه استفاده کنید.
برای اطلاعات بیشتر، راهنمای
ایجاد نشانگرها را بخوانید.
افزودن خوشهکننده نشانگرها
برای افزودن خوشهکننده نشانگرها، این مراحل را دنبال کنید:
- کتابخانه خوشهبندی نشانگر را به صفحه یا برنامهتان اضافه کنید. این کتابخانه در NPM در
@googlemaps/markerclusterer
و در
مخزن GitHub دردسترس است.
یکی از گزینههای زیر را برای نصب کتابخانه انتخاب کنید:
NPM
کتابخانه @googlemaps/markerclusterer را بااستفاده از NPM نصب کنید.
npm install @googlemaps/markerclusterer
CDN
دستور زیر کتابخانه را از unpkg.com CDN بار میکند.
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
- خوشهکننده نشانگرها را در برنامهتان اضافه کنید.
کد زیر یک خوشهبندی نشانگر به نقشه اضافه میکند. یکی از گزینههای زیر را انتخاب کنید:
NPM
import { MarkerClusterer } from "@googlemaps/markerclusterer"; const markerCluster = new MarkerClusterer({ markers, map });
CDN
وقتی با CDN دسترسی پیدا میکنید، کتابخانه تحت
markerClustererglobal دردسترس است.const markerCluster = new markerClusterer.MarkerClusterer({ markers, map });
این نمونه آرایه
markersرا بهMarkerClustererارسال میکند. - (اختیاری) سفارشیسازی خوشهبندی نشانگر برای تغییر ظاهر نماد خوشه، الگوریتم خوشهبندی، یا رفتار کلیک.
سفارشیسازی خوشهبندی نشانگر
میتوانید MarkerClusterer را با ارسال گزینههای اضافی در شیء
MarkerClustererOptions
هنگام ایجاد نمونه خوشهبندی سفارشیسازی کنید:
renderer: نحوه پرداز کردن نشانگرهای خوشه در نقشه را کنترل میکند.algorithmوalgorithmOptions: نحوه گروهبندی نشانگرها در خوشهها براساس فاصله و سطح بزرگنمایی را کنترل میکند.onClusterClick: رفتار پیشفرض را وقتی کاربر روی نشانگر خوشه کلیک میکند لغو میکند.
نقشه زیر نشانگرهایی با نمادهای خوشه سفارشی را نشان میدهد.
سفارشیسازی نماد خوشه
بهطور پیشفرض، MarkerClusterer از
DefaultRenderer
برای ایجاد نمادهای خوشه SVG استفاده میکند. برای سفارشیسازی ظاهر نشانگرهای خوشه، شیئی را که واسط کاربر
Renderer
را پیادهسازی میکند به گزینه renderer ارسال کنید.
میانای Renderer به روش render(cluster, stats, map) نیاز دارد که
AdvancedMarkerElement (یا google.maps.Marker قدیمی) را برمیگرداند. روش render آرگومانهای زیر را دریافت میکند:
-
cluster(Cluster): نشاندهنده گروه نشانگرها، ازجمله:count: تعداد نشانگرهای نمایان در خوشه.-
position: موقعیت مرکزgoogle.maps.LatLngخوشه. -
bounds:google.maps.LatLngBoundsخوشه. -
markers: آرایه نشانگرهای موجود در خوشه.
-
stats(ClusterStats): آمار را در همه خوشهها در چرخه پرداز کنونی ارائه میدهد، مثلاًstats.clusters.markers.mean،min،max، وsum. میتوانید از این مقادیر برای سبکدهی پویای خوشهها براساس اندازه نسبی آنها استفاده کنید. -
map(google.maps.Map): نمونه نقشه.
مثال زیر renderer سفارشی را تعریف میکند که نشان HTML دایرهای برای هر
AdvancedMarkerElement میسازد و وقتی تعداد نشانگر خوشهای از اندازه میانگین خوشه فراتر میرود، رنگ پسزمینه آن را تغییر میدهد.
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 }); }
دیدن نمونه کامل پردازنده سفارشی
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>برای نمونههای بیشتر از رندرکننده، نمونه نمایشی استفاده از رندرکننده را ببینید.
اصلاح الگوریتم خوشهبندی
بهطور پیشفرض، MarkerClusterer از
SuperClusterAlgorithm,
استفاده میکند
که نشانگرها را بااستفاده از درختهای k-d خوشهبندی میکند. میتوانید رفتار خوشهبندی را با ارسال نمونه الگوریتم به گزینه algorithm، یا با ارسال گزینههای الگوریتم پیشفرض به algorithmOptions سفارشیسازی کنید.
این کتابخانه شامل الگوریتمهای داخلی زیر است:
SuperClusterAlgorithm(پیشفرض): خوشهبندی سریع نقطهای زمینمکانی بااستفاده از درختهای k-d. گزینههای رایج عبارتاند از:maxZoom: حداکثر سطح بزرگنمایی که در آن خوشهها تولید میشوند (پیشفرض:16). در سطوح بزرگنمایی بالاتر ازmaxZoom، نشانگرهای تکی بدون خوشهبندی نمایش داده میشوند.radius: شعاع خوشه به پیکسل (پیشفرض:60).minPoints: حداقل تعداد نشانگرهای لازم برای تشکیل خوشه (پیشفرض:2).
SuperClusterViewportAlgorithm: یک نوع فیلترشده ازSuperClusterAlgorithmکه فقط نشانگرها را در نمای فعلی نقشه بهعلاوه یک حاشیه پیکسلی (viewportPadding، پیشفرض:60) خوشهبندی میکند.GridAlgorithm: الگوریتم خوشهبندی مبتنی بر شبکه. گزینههای رایج شاملmaxZoom(پیشفرض:16)،gridSize(اندازه شبکه در پیکسل، پیشفرض:40)، وmaxDistance(حداکثر فاصله بین مرکز خوشه و نشانگر، پیشفرض:40000) میشود.NoopAlgorithm: همه نشانگرها را بدون خوشهبندی کردن آنها ارائه میکند.
پیکربندی مثال زیر SuperClusterAlgorithm با radius و maxZoom سفارشی:
import {MarkerClusterer, SuperClusterAlgorithm} from '@googlemaps/markerclusterer'; const markerCluster = new MarkerClusterer({ map, markers, algorithm: new SuperClusterAlgorithm({ radius: 80, maxZoom: 15, }), });
برای مقایسه نحوه رفتار الگوریتمهای مختلف، نسخه نمایشی مقایسه الگوریتم را ببینید.
سفارشیسازی رفتار کلیک کردن روی خوشه
بهطور پیشفرض، با کلیک کردن روی نشانگر خوشه، نمای درگاه نقشه با محدوده خوشه کلیکشده مطابقت داده میشود
(map.fitBounds(cluster.bounds)). برای سفارشیسازی کردن آنچه هنگام کلیک کاربر روی خوشه اتفاق میافتد، تابع برگشتی را به گزینه
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); }, });
بیشتر بدانید
میتوانید نمونههای پیچیدهتری از خوشهبندی نشانگرها را در مخزن GitHub مشاهده کنید و اسناد مرجع کتابخانه را بخوانید.