API di Google Maps V3: più marcatori nello stesso punto esatto


115

Bit bloccato su questo. Sto recuperando un elenco di coordinate geografiche tramite JSON e inserendole in una mappa di Google. Funziona tutto bene tranne nel caso in cui ho due o più marker nello stesso identico punto. L'API mostra solo 1 indicatore: quello in alto. Questo è abbastanza giusto suppongo, ma vorrei trovare un modo per mostrarli tutti in qualche modo.

Ho cercato su Google e ho trovato alcune soluzioni, ma per lo più sembrano essere per V2 dell'API o semplicemente non eccezionali. Idealmente, vorrei una soluzione in cui fai clic su una sorta di indicatore di gruppo e che quindi mostri gli indicatori raggruppati attorno al punto in cui si trovano.

Qualcuno ha avuto questo problema o qualcosa di simile e vorrebbe condividere una soluzione?

Risposte:


116

Dai un'occhiata a OverlappingMarkerSpiderfier .
C'è una pagina demo, ma non mostrano i marker che si trovano esattamente nello stesso punto, solo alcuni che sono molto vicini tra loro.

Ma un esempio di vita reale con marcatori nello stesso identico punto può essere visto su http://www.ejw.de/ejw-vor-ort/ (scorrere verso il basso per la mappa e fare clic su alcuni marcatori per vedere l'effetto ragno ).

Questa sembra essere la soluzione perfetta per il tuo problema.


Questo è fantastico e soddisfa perfettamente le esigenze lasciate dalla libreria del cluster, che non gestisce i marker con la stessa esatta latitudine / lunghezza.
Justin

Se si utilizza OverlappingMarkerSpiderfierin combinazione con MarkerClusterer, una buona opzione è impostare l' maxZoomopzione sul costruttore del cluster. Questo "rimuove i cluster" dai marker dopo il livello di zoom specificato.
Diederik

@Tad Non so se sei lo sviluppatore di quel sito web, ma volevo farti sapere che la mappa su ejw.de è rotta. Ottengo un errore JS.
Alex

Ho controllato la demo proposta, ma sembra morta. Dopo ulteriori ricerche ho trovato questo esempio su come integrare cluster di marker e Spiderifier. Nessuna demo live, ma basta clonare e seguire il file readme. github.com/yagoferrer/markerclusterer-plus-spiderfier-example
Sax

34

L'offset dei marker non è una vera soluzione se si trovano nello stesso edificio. Quello che potresti voler fare è modificare markerclusterer.js in questo modo:

  1. Aggiungi un metodo di clic prototipo nella classe MarkerClusterer, in questo modo - lo sovrascriveremo più avanti nella funzione map initialize ():

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
  2. Nella classe ClusterIcon, aggiungi il codice seguente DOPO il trigger clusterclick:

    // Trigger the clusterclick event.
    google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
    
    var zoom = this.map_.getZoom();
    var maxZoom = markerClusterer.getMaxZoom();
    // if we have reached the maxZoom and there is more than 1 marker in this cluster
    // use our onClick method to popup a list of options
    if (zoom >= maxZoom && this.cluster_.markers_.length > 1) {
       return markerClusterer.onClickZoom(this);
    }
  3. Quindi, nella funzione initialize () in cui inizializzi la mappa e dichiari il tuo oggetto MarkerClusterer:

    markerCluster = new MarkerClusterer(map, markers);
    // onClickZoom OVERRIDE
    markerCluster.onClickZoom = function() { return multiChoice(markerCluster); }

    Dove multiChoice () è la TUA (ancora da scrivere) funzione per visualizzare una InfoWindow con un elenco di opzioni tra cui scegliere. Nota che l'oggetto markerClusterer viene passato alla tua funzione, perché ti servirà per determinare quanti marker ci sono in quel cluster. Per esempio:

    function multiChoice(mc) {
         var cluster = mc.clusters_;
         // if more than 1 point shares the same lat/long
         // the size of the cluster array will be 1 AND
         // the number of markers in the cluster will be > 1
         // REMEMBER: maxZoom was already reached and we can't zoom in anymore
         if (cluster.length == 1 && cluster[0].markers_.length > 1)
         {
              var markers = cluster[0].markers_;
              for (var i=0; i < markers.length; i++)
              {
                  // you'll probably want to generate your list of options here...
              }
    
              return false;
         }
    
         return true;
    }

17

L'ho usato insieme a jQuery e fa il lavoro:

var map;
var markers = [];
var infoWindow;

function initialize() {
    var center = new google.maps.LatLng(-29.6833300, 152.9333300);

    var mapOptions = {
        zoom: 5,
        center: center,
        panControl: false,
        zoomControl: false,
        mapTypeControl: false,
        scaleControl: false,
        streetViewControl: false,
        overviewMapControl: false,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      }


    map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

    $.getJSON('jsonbackend.php', function(data) {
        infoWindow = new google.maps.InfoWindow();

        $.each(data, function(key, val) {
            if(val['LATITUDE']!='' && val['LONGITUDE']!='')
            {                
                // Set the coordonates of the new point
                var latLng = new google.maps.LatLng(val['LATITUDE'],val['LONGITUDE']);

                //Check Markers array for duplicate position and offset a little
                if(markers.length != 0) {
                    for (i=0; i < markers.length; i++) {
                        var existingMarker = markers[i];
                        var pos = existingMarker.getPosition();
                        if (latLng.equals(pos)) {
                            var a = 360.0 / markers.length;
                            var newLat = pos.lat() + -.00004 * Math.cos((+a*i) / 180 * Math.PI);  //x
                            var newLng = pos.lng() + -.00004 * Math.sin((+a*i) / 180 * Math.PI);  //Y
                            var latLng = new google.maps.LatLng(newLat,newLng);
                        }
                    }
                }

                // Initialize the new marker
                var marker = new google.maps.Marker({map: map, position: latLng, title: val['TITLE']});

                // The HTML that is shown in the window of each item (when the icon it's clicked)
                var html = "<div id='iwcontent'><h3>"+val['TITLE']+"</h3>"+
                "<strong>Address: </strong>"+val['ADDRESS']+", "+val['SUBURB']+", "+val['STATE']+", "+val['POSTCODE']+"<br>"+
                "</div>";

                // Binds the infoWindow to the point
                bindInfoWindow(marker, map, infoWindow, html);

                // Add the marker to the array
                markers.push(marker);
            }
        });

        // Make a cluster with the markers from the array
        var markerCluster = new MarkerClusterer(map, markers, { zoomOnClick: true, maxZoom: 15, gridSize: 20 });
    });
}

function markerOpen(markerid) {
    map.setZoom(22);
    map.panTo(markers[markerid].getPosition());
    google.maps.event.trigger(markers[markerid],'click');
    switchView('map');
}

google.maps.event.addDomListener(window, 'load', initialize);

Thx funziona perfettamente per me :) Proprio quello che stavo cercando da ore: p
Jicao

Soluzione bella ed elegante. Grazie!
Concept211

Soluzione perfetta che compensa un po '! È possibile, passiamo il mouse sull'indicatore e quindi visualizza più indicatori
Intekhab Khan,

14

Espandendo la risposta di Chaoley , ho implementato una funzione che, dato un elenco di posizioni (oggetti con lnge latproprietà) le cui coordinate sono esattamente le stesse, le allontana un po 'dalla loro posizione originale (modificando gli oggetti in posizione). Quindi formano un bel cerchio attorno al punto centrale.

Ho scoperto che, per la mia latitudine (52 gradi nord), 0,0003 gradi di raggio del cerchio funzionano meglio e che devi compensare la differenza tra latitudine e longitudine quando convertiti in chilometri. Puoi trovare conversioni approssimative per la tua latitudine qui .

var correctLocList = function (loclist) {
    var lng_radius = 0.0003,         // degrees of longitude separation
        lat_to_lng = 111.23 / 71.7,  // lat to long proportion in Warsaw
        angle = 0.5,                 // starting angle, in radians
        loclen = loclist.length,
        step = 2 * Math.PI / loclen,
        i,
        loc,
        lat_radius = lng_radius / lat_to_lng;
    for (i = 0; i < loclen; ++i) {
        loc = loclist[i];
        loc.lng = loc.lng + (Math.cos(angle) * lng_radius);
        loc.lat = loc.lat + (Math.sin(angle) * lat_radius);
        angle += step;
    }
};

1
DzinX, ho passato le ultime 4 ore a cercare di capire come implementare il tuo codice nel mio codice senza alcun risultato. Sono giunto alla conclusione che faccio schifo in questo e apprezzerei davvero il tuo aiuto. Qui è dove sto cercando di inserire il codice: pastebin.com/5qYbvybm - QUALSIASI AIUTO sarebbe apprezzato! Grazie!
WebMW

WebMW: dopo aver estratto i marcatori da XML, è necessario prima raggrupparli in elenchi in modo che ogni elenco contenga marcatori che puntano esattamente alla stessa posizione. Quindi, in ogni elenco che contiene più di un elemento, eseguire correctLocList (). Solo dopo averlo fatto, crea gli oggetti google.maps.Marker.
DzinX

La sua non funziona .... Si cambia solo mia anela lat da 37.68625400000000000,-75.71669800000000000a 37.686254,-75.716698anche che è lo stesso per tutti i valori ... M aspettavo qualcosa del genere ... 37.68625400000000000,-75.71669800000000000per 37.6862540000001234,-75.7166980000001234 37.6862540000005678,-75.7166980000005678..ecc .. Ho anche provato a cambiare il mio punto di vista.
Premshankar Tiwari

Hummm, scaviamo un po 'e speriamo che @DzinX sia ancora in zona. Puoi mate (o qualcun altro) spiegare come si ottengono tutti quei numeri? Lng_radius, ecc? Grazie mille. :)
Vae

1
@ Vae: il raggio è quanto vuoi che sia grande il cerchio. Vuoi che il cerchio si guardi intorno in pixel, quindi devi sapere, nella tua posizione, qual è la proporzione tra 1 grado di latitudine e 1 grado di longitudine (in pixel sulla mappa). Puoi trovarlo su alcuni siti Web o semplicemente sperimentare finché non ottieni il numero corretto. L'angolo è quanto il primo perno è a destra dall'alto.
DzinX

11

@Ignatius risposta eccellente, aggiornata per funzionare con v2.0.7 di MarkerClustererPlus.

  1. Aggiungi un metodo di clic prototipo nella classe MarkerClusterer, in questo modo - lo sovrascriveremo più avanti nella funzione map initialize ():

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
  2. Nella classe ClusterIcon, aggiungi il codice seguente DOPO il trigger click / clusterclick:

    // EXISTING CODE (around line 143)
    google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
    google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
    
    // BEGIN MODIFICATION
    var zoom = mc.getMap().getZoom();
    // Trying to pull this dynamically made the more zoomed in clusters not render
    // when then kind of made this useless. -NNC @ BNB
    // var maxZoom = mc.getMaxZoom();
    var maxZoom = 15;
    // if we have reached the maxZoom and there is more than 1 marker in this cluster
    // use our onClick method to popup a list of options
    if (zoom >= maxZoom && cClusterIcon.cluster_.markers_.length > 1) {
        return mc.onClick(cClusterIcon);
    }
    // END MODIFICATION
  3. Quindi, nella funzione initialize () in cui inizializzi la mappa e dichiari il tuo oggetto MarkerClusterer:

    markerCluster = new MarkerClusterer(map, markers);
    // onClick OVERRIDE
    markerCluster.onClick = function(clickedClusterIcon) { 
      return multiChoice(clickedClusterIcon.cluster_); 
    }

    Dove multiChoice () è la TUA (ancora da scrivere) funzione per visualizzare una InfoWindow con un elenco di opzioni tra cui scegliere. Nota che l'oggetto markerClusterer viene passato alla tua funzione, perché ti servirà per determinare quanti marker ci sono in quel cluster. Per esempio:

    function multiChoice(clickedCluster) {
      if (clickedCluster.getMarkers().length > 1)
      {
        // var markers = clickedCluster.getMarkers();
        // do something creative!
        return false;
      }
      return true;
    };

1
Grazie, funziona perfettamente! Per quanto riguarda il tuo maxZoomproblema, penso che questo sia un problema solo se non è impostato maxZoom o maxZoom per il cluster è maggiore dello zoom per la mappa. Ho sostituito il tuo hardcoded con questo snippet e sembra funzionare bene:var maxZoom = mc.getMaxZoom(); if (null === maxZoom || maxZoom > mc.getMap().maxZoom) { maxZoom = mc.getMap().maxZoom; if (null === maxZoom) { maxZoom = 15; } }
Pascal

So che è molto vecchio, ma sto cercando di utilizzare questa soluzione. Ho funzionato, ma nel caso di mostrare una finestra informativa con i dati del cluster ... come faccio a ottenere la posizione del marker del cluster che è stato cliccato in primo luogo in modo da poter mostrare la finestra informativa? markers è il mio array di dati ... ma come posso mostrare la finestra delle informazioni sull'icona / marker del cluster?
user756659

@ user756659 in multiChoice ottieni lat / lng tramite: clickedCluster.center_.lat()/clickedCluster.center_.lng()
Jens Kohl

1
@Pascal e con un po 'di offuscamento finiamo con questo, che potrebbe funzionare, ma come dice il tao di Python. "la leggibilità conta". var maxZoom = Math.min (mc.getMaxZoom () || 15, mc.getMap (). maxZoom || 15);
Nande

6

Le risposte sopra sono più eleganti, ma ho trovato un modo veloce e sporco che funziona davvero davvero incredibilmente bene. Puoi vederlo in azione su www.buildinglit.com

Tutto quello che ho fatto è stato aggiungere un offset casuale alla latitudine e alla longditudine alla mia pagina genxml.php in modo che restituisca risultati leggermente diversi ogni volta con offset ogni volta che la mappa viene creata con i marcatori. Sembra un trucco, ma in realtà è sufficiente che i marcatori spostino una leggera spinta in una direzione casuale affinché siano cliccabili sulla mappa se si sovrappongono. In realtà funziona davvero bene, direi meglio del metodo ragno perché chi vuole affrontare quella complessità e farle balzare ovunque. Vuoi solo essere in grado di selezionare il marcatore. Spingerlo in modo casuale funziona perfettamente.

Ecco un esempio della creazione del nodo di iterazione dell'istruzione while nel mio php_genxml.php

while ($row = @mysql_fetch_assoc($result)){ $offset = rand(0,1000)/10000000;
$offset2 = rand(0, 1000)/10000000;
$node = $dom->createElement("marker");
$newnode = $parnode->appendChild($node);
$newnode->setAttribute("name", $row['name']);
$newnode->setAttribute("address", $row['address']);
$newnode->setAttribute("lat", $row['lat'] + $offset);
$newnode->setAttribute("lng", $row['lng'] + $offset2);
$newnode->setAttribute("distance", $row['distance']);
$newnode->setAttribute("type", $row['type']);
$newnode->setAttribute("date", $row['date']);
$newnode->setAttribute("service", $row['service']);
$newnode->setAttribute("cost", $row['cost']);
$newnode->setAttribute("company", $company);

Nota sotto lat e long c'è il + offset. dalle 2 variabili sopra. Ho dovuto dividere a caso per 0,1000 per 10000000 per ottenere un decimale che fosse abbastanza piccolo a caso da spostare a malapena i marcatori. Sentiti libero di armeggiare con quella variabile per ottenerne una più precisa per le tue esigenze.


Freddo! questo è il trucco sporco che ho trovato molto utile, non è necessario scherzare con il codice api di Google Maps
CuongDC

3

Per le situazioni in cui sono presenti più servizi nello stesso edificio, è possibile spostare gli indicatori solo un po ', (diciamo di 0,001 gradi), in un raggio dal punto effettivo. Questo dovrebbe anche produrre un bell'effetto visivo.


3

Questa è più una soluzione provvisoria "rapida e sporca" simile a quella suggerita da Matthew Fox, questa volta utilizzando JavaScript.

In JavaScript puoi semplicemente compensare la latitudine e la lunghezza di tutte le tue posizioni aggiungendo un piccolo offset casuale a entrambe, ad es

myLocation[i].Latitude+ = (Math.random() / 25000)

(Ho scoperto che la divisione per 25000 fornisce una separazione sufficiente ma non sposta il marcatore in modo significativo dalla posizione esatta, ad esempio un indirizzo specifico)

Questo fa un buon lavoro per compensarli l'uno dall'altro, ma solo dopo aver ingrandito da vicino. Quando si rimpicciolisce lo zoom, non sarà ancora chiaro che ci sono più opzioni per la posizione.


1
Mi piace questo. Se non hai bisogno di marker accuratamente rappresentativi, basta abbassare 25000 a 250 o 25. Soluzione semplice e rapida.
Fid

2

Dai un'occhiata a Marker Clusterer per V3: questa libreria raggruppa i punti vicini in un indicatore di gruppo. La mappa si ingrandisce quando si fa clic sui cluster. Immagino che quando si ingrandisce a destra avresti comunque lo stesso problema con i marcatori nello stesso punto.


Sì, ho dato un'occhiata a quella piccola libreria utile ma ancora non sembra superare il problema. Sto creando un localizzatore di servizi per un'azienda che a volte ha più di un servizio nello stesso blocco di uffici e quindi le stesse identiche coordinate geografiche. Posso mostrare i diversi servizi in un'unica finestra
informativa,

@Louzoid Marker Clusterer non risolve il problema come ho appena scoperto. Ho lo stesso problema, ho diverse aziende all'interno di un edificio e quindi mostra solo 1 marker.
Rob il

1

Aggiornato per funzionare con MarkerClustererPlus.

  google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
  google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name

  // BEGIN MODIFICATION
  var zoom = mc.getMap().getZoom();
  // Trying to pull this dynamically made the more zoomed in clusters not render
  // when then kind of made this useless. -NNC @ BNB
  // var maxZoom = mc.getMaxZoom();
  var maxZoom = 15;
  // if we have reached the maxZoom and there is more than 1 marker in this cluster
  // use our onClick method to popup a list of options
  if (zoom >= maxZoom && cClusterIcon.cluster_.markers_.length > 1) {
    var markers = cClusterIcon.cluster_.markers_;
    var a = 360.0 / markers.length;
    for (var i=0; i < markers.length; i++)
    {
        var pos = markers[i].getPosition();
        var newLat = pos.lat() + -.00004 * Math.cos((+a*i) / 180 * Math.PI);  // x
        var newLng = pos.lng() + -.00004 * Math.sin((+a*i) / 180 * Math.PI);  // Y
        var finalLatLng = new google.maps.LatLng(newLat,newLng);
        markers[i].setPosition(finalLatLng);
        markers[i].setMap(cClusterIcon.cluster_.map_);
    }
    cClusterIcon.hide();
    return ;
  }
  // END MODIFICATION

Questo approccio richiede che l'utente faccia clic sull'icona del cluster e non coprirà i casi d'uso in cui la navigazione viene eseguita con il cursore dello zoom o lo scorrimento del mouse. Qualche idea su come affrontare questi problemi?
Remi Sture

1

Mi piacciono le soluzioni semplici quindi ecco la mia. Invece di modificare il lib, il che renderebbe più difficile la manutenzione. puoi semplicemente guardare l'evento in questo modo

google.maps.event.addListener(mc, "clusterclick", onClusterClick);

quindi puoi gestirlo

function onClusterClick(cluster){
    var ms = cluster.getMarkers();

i, cioè, ho usato bootstrap per mostrare un pannello con un elenco. che trovo molto più comodo e utilizzabile di spiderfying in luoghi "affollati". (se stai usando un clusterer, è probabile che finirai con delle collisioni una volta che avrai eseguito lo spiderfy). puoi controllare lo zoom anche lì.

btw. ho appena trovato il volantino e sembra funzionare molto meglio, il cluster E spiderfy funziona in modo molto fluido http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html ed è open source.



0

Espandendo le risposte fornite sopra, assicurati di impostare l'opzione maxZoom durante l'inizializzazione dell'oggetto mappa.


0

Dare un offset allontanerà i marker quando l'utente ingrandisce al massimo. Quindi ho trovato un modo per ottenerlo. questo potrebbe non essere un modo corretto, ma ha funzionato molto bene.

// This code is in swift
for loop markers
{
//create marker
let mapMarker = GMSMarker()
mapMarker.groundAnchor = CGPosition(0.5, 0.5)
mapMarker.position = //set the CLLocation
//instead of setting marker.icon set the iconView
let image:UIIMage = UIIMage:init(named:"filename")
let imageView:UIImageView = UIImageView.init(frame:rect(0,0, ((image.width/2 * markerIndex) + image.width), image.height))
imageView.contentMode = .Right
imageView.image = image
mapMarker.iconView = imageView
mapMarker.map = mapView
}

imposta lo zIndex del marker in modo da vedere l'icona del marker che vuoi vedere in alto, altrimenti animerà i marker come lo scambio automatico. quando l'utente tocca il marker, gestisce lo zIndex per portare il marker in primo piano utilizzando zIndex Swap.


0

Come farla franca .. [Swift]

    var clusterArray = [String]()
    var pinOffSet : Double = 0
    var pinLat = yourLat
    var pinLong = yourLong
    var location = pinLat + pinLong

Sta per essere creato un nuovo marker? controlla clusterArraye manipola l'offset

 if(!clusterArray.contains(location)){
        clusterArray.append(location)
    } else {

        pinOffSet += 1
        let offWithIt = 0.00025 // reasonable offset with zoomLvl(14-16)
        switch pinOffSet {
        case 1 : pinLong = pinLong + offWithIt ; pinLat = pinLat + offWithIt
        case 2 : pinLong = pinLong + offWithIt ; pinLat = pinLat - offWithIt
        case 3 : pinLong = pinLong - offWithIt ; pinLat = pinLat - offWithIt
        case 4 : pinLong = pinLong - offWithIt ; pinLat = pinLat + offWithIt
        default : print(1)
        }


    }

risultato

inserisci qui la descrizione dell'immagine


0

Aggiungendo alla risposta subdola geniale di Matthew Fox, ho aggiunto un piccolo offset casuale a ciascun lat e lng durante l'impostazione dell'oggetto marker. Per esempio:

new LatLng(getLat()+getMarkerOffset(), getLng()+getMarkerOffset()),

private static double getMarkerOffset(){
    //add tiny random offset to keep markers from dropping on top of themselves
    double offset =Math.random()/4000;
    boolean isEven = ((int)(offset *400000)) %2 ==0;
    if (isEven) return  offset;
    else        return -offset;
}

-2

Estendendo le risposte di cui sopra, quando si ottengono stringhe unite, posizione non aggiunta / sottratta (ad esempio "37.12340-0.00069"), convertire la latitudine / longitudine originale in float, ad esempio utilizzando parseFloat (), quindi aggiungere o sottrarre correzioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.