Przegląd
Z tego samouczka dowiesz się, jak wyświetlać dużą liczbę znaczników na mapie za pomocą rozwiązania do łączenia znaczników w klastry. Aby łączyć blisko położone znaczniki w klastry i uprościć wyświetlanie znaczników na mapie, możesz użyć biblioteki @googlemaps/markerclusterer w połączeniu z Maps JavaScript API.
Aby zobaczyć, jak działa grupowanie znaczników, wyświetl tę mapę.
Liczba na klastrze wskazuje, ile znaczników zawiera. Zauważ, że gdy powiększysz dowolną lokalizację klastra, liczba na klastrze zmniejszy się i zaczniesz widzieć poszczególne znaczniki na mapie. Pomniejszenie mapy spowoduje ponowne połączenie znaczników w klastry.
Poniższy przykład pokazuje pełny kod potrzebny do utworzenia tej mapy.
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>W tym samouczku dodamy do mapy zestaw znaczników za pomocą tablicy locations. Do pobierania znaczników na mapę możesz używać innych źródeł.
Więcej informacji znajdziesz w przewodniku po
tworzeniu znaczników.
Dodawanie rozwiązania do łączenia znaczników w klastry
Aby dodać rozwiązanie do łączenia znaczników w klastry:
- Dodaj bibliotekę grupowania znaczników do strony lub aplikacji. Biblioteka jest dostępna w NPM pod adresem
@googlemaps/markerclusterer
oraz w
repozytorium w GitHubie.
Aby zainstalować bibliotekę, wybierz jedną z tych opcji:
NPM
Zainstaluj bibliotekę @googlemaps/markerclusterer za pomocą NPM.
npm install @googlemaps/markerclusterer
CDN
Ten skrypt wczytuje bibliotekę z CDN unpkg.com.
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
- Dodaj rozwiązanie do łączenia znaczników w klastry w aplikacji.
Ten kod dodaje do mapy rozwiązanie do łączenia znaczników w klastry. Wybierz jedną z tych opcji:
NPM
import { MarkerClusterer } from "@googlemaps/markerclusterer"; const markerCluster = new MarkerClusterer({ markers, map });
CDN
Gdy biblioteka jest dostępna przez CDN, jest dostępna w globalnym obiekcie
markerClusterer.const markerCluster = new markerClusterer.MarkerClusterer({ markers, map });
Ten przykład przekazuje tablicę
markersdoMarkerClusterer. - (Opcjonalnie) Dostosuj klaster znaczników, aby zmienić wygląd ikony klastra, algorytm grupowania lub zachowanie po kliknięciu.
Dostosowywanie rozwiązania do łączenia znaczników w klastry
Możesz dostosować MarkerClusterer, przekazując dodatkowe opcje w
MarkerClustererOptions
obiekcie podczas tworzenia instancji klastra:
renderer: określa, jak znaczniki klastra są renderowane na mapie.algorithmialgorithmOptions: określają, jak znaczniki są grupowane w klastry na podstawie odległości i poziomu powiększenia.onClusterClick: Zastępuje działanie domyślne, gdy użytkownik kliknie znacznik klastra.
Na tej mapie widać znaczniki z niestandardowymi ikonami klastrów.
Dostosowywanie ikony klastra
Domyślnie MarkerClusterer używa
DefaultRenderer
do tworzenia ikon klastrów SVG. Aby dostosować wygląd znaczników klastra, przekaż do opcji renderer obiekt, który implementuje interfejs
Renderer.
Interfejs Renderer wymaga metody render(cluster, stats, map), która zwraca
AdvancedMarkerElement (lub starszy google.maps.Marker). Metoda render przyjmuje te argumenty:
cluster(Cluster): reprezentuje grupę znaczników, w tym:count: liczba widocznych znaczników w klastrze.position: pozycja środka klastragoogle.maps.LatLng.bounds:google.maps.LatLngBoundsklastra.markers: tablica znaczników zawartych w klastrze.
stats(ClusterStats): zawiera statystyki dotyczące wszystkich klastrów w bieżącym cyklu renderowania, takie jakstats.clusters.markers.mean,min,max, isum. Możesz użyć tych wartości, aby dynamicznie stylizować klastry na podstawie ich względnego rozmiaru.map(google.maps.Map): instancja mapy.
Ten przykład definiuje niestandardowy renderer który tworzy okrągłą odznakę HTML dla każdego
AdvancedMarkerElement i zmienia jej kolor tła, gdy liczba znaczników klastra przekracza średni rozmiar klastra.
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 }); }
Zobacz pełny przykład niestandardowego renderera
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>Więcej przykładów renderera znajdziesz w wersji demonstracyjnej użycia renderera.
Modyfikowanie algorytmu grupowania
Domyślnie MarkerClusterer używa
SuperClusterAlgorithm,
który grupuje znaczniki za pomocą drzew k-d. Możesz dostosować zachowanie grupowania, przekazując instancję algorytmu do opcji
algorithm lub przekazując opcje algorytmu domyślnego do algorithmOptions.
Biblioteka zawiera te wbudowane algorytmy:
SuperClusterAlgorithm(domyślny): szybkie grupowanie punktów geoprzestrzennych za pomocą drzew k-d. Typowe opcje:maxZoom: maksymalny poziom powiększenia, przy którym generowane są klastry (domyślnie:16). Przy poziomach powiększenia powyżejmaxZoomposzczególne znaczniki są wyświetlane bez grupowania.radius: promień klastra w pikselach (domyślnie:60).minPoints: minimalna liczba znaczników wymagana do utworzenia klastra (domyślnie:2).
SuperClusterViewportAlgorithm: WariantSuperClusterAlgorithmfiltrowany według widocznego obszaru, który grupuje tylko znaczniki w bieżącym widocznym obszarze mapy oraz dopełnienie w pikselach (viewportPadding, domyślnie:60).GridAlgorithm: algorytm grupowania oparty na siatce. Typowe opcje tomaxZoom(domyślnie:16),gridSize(rozmiar siatki w pikselach, domyślnie:40) imaxDistance(maksymalna odległość w metrach między środkiem klastra a znacznikiem, domyślnie:40000).NoopAlgorithm: renderuje wszystkie znaczniki bez grupowania.
Ten przykład konfiguruje SuperClusterAlgorithm z niestandardowymi wartościami radius i maxZoom:
import {MarkerClusterer, SuperClusterAlgorithm} from '@googlemaps/markerclusterer'; const markerCluster = new MarkerClusterer({ map, markers, algorithm: new SuperClusterAlgorithm({ radius: 80, maxZoom: 15, }), });
Aby porównać zachowanie różnych algorytmów, zobacz wersję demonstracyjną porównania algorytmów.
Dostosowywanie zachowania po kliknięciu klastra
Domyślnie kliknięcie znacznika klastra powoduje dopasowanie widocznego obszaru mapy do granic klikniętego klastra
(map.fitBounds(cluster.bounds)). Aby dostosować zachowanie po kliknięciu klastra przez użytkownika, przekaż funkcję wywołania zwrotnego do
onClusterClick
opcji:
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); }, });
Więcej informacji
Bardziej złożone przykłady grupowania znaczników znajdziesz w repozytorium w GitHubie. Możesz też przeczytać dokumentację referencyjną biblioteki.