نظرة عامة
يوضّح هذا البرنامج التعليمي كيفية استخدام مجموعات محدّدات المواقع لعرض عدد كبير من محدّدات المواقع على خريطة. يمكنك استخدام مكتبة @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
يحمِّل النص البرمجي التالي المكتبة من شبكة عرض المحتوى (CDN) على unpkg.com.
<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)، تتوفّر ضمن المتغيّر العام
markerClusterer.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 وقراءة الـ مستندات المرجعية للمكتبة.