API V3 do Google Maps - vários marcadores exatamente no mesmo local

115

Um pouco preso neste. Estou recuperando uma lista de geo coords via JSON e colocando-os em um mapa do Google. Tudo está funcionando bem, exceto no caso em que tenho dois ou mais marcadores exatamente no mesmo local. A API exibe apenas 1 marcador - o primeiro. Suponho que seja justo, mas gostaria de encontrar uma maneira de exibi-los todos de alguma forma.

Eu pesquisei no google e encontrei algumas soluções, mas a maioria delas parece ser para a V2 da API ou simplesmente não tão boa. Idealmente, eu gostaria de uma solução em que você clica em algum tipo de marcador de grupo e mostra os marcadores agrupados ao redor do local em que estão todos.

Alguém teve esse problema ou algo semelhante e gostaria de compartilhar uma solução?

Louzoid
fonte

Respostas:

116

Dê uma olhada em OverlappingMarkerSpiderfier .
Há uma página de demonstração, mas eles não mostram marcadores que estão exatamente no mesmo local, apenas alguns que estão muito próximos.

Mas um exemplo da vida real com marcadores exatamente no mesmo local pode ser visto em http://www.ejw.de/ejw-vor-ort/ (role para baixo para o mapa e clique em alguns marcadores para ver o efeito de aranha )

Essa parece ser a solução perfeita para o seu problema.

Tad Wohlrapp
fonte
Isso é incrível e atende perfeitamente à necessidade deixada pela biblioteca de cluster, que não lida com marcadores com a mesma latitude / longitude exata.
Justin
Se estiver usando OverlappingMarkerSpiderfierem combinação com MarkerClusterer, uma boa opção é definir a maxZoomopção no construtor de cluster. Isso "unclusters" os marcadores após o nível de zoom especificado.
Diederik
@Tad Não sei se você é o desenvolvedor desse site, mas gostaria que soubesse que o mapa em ejw.de está quebrado. Recebo um erro JS.
Alex
Verifiquei a demonstração proposta, mas parece morta. Depois de mais pesquisas, encontrei este exemplo de como integrar clusters de marcadores e Spiderifier. Nenhuma demonstração ao vivo, apenas clonar e seguir o readme. github.com/yagoferrer/markerclusterer-plus-spiderfier-example
Sax
34

Compensar os marcadores não é uma solução real se eles estiverem localizados no mesmo prédio. O que você pode querer fazer é modificar o markerclusterer.js assim:

  1. Adicione um método de clique de protótipo na classe MarkerClusterer, assim - vamos substituir isso mais tarde na função initialize () do mapa:

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
  2. Na classe ClusterIcon, adicione o seguinte código APÓS o gatilho 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. Então, em sua função initialize () onde você inicializa o mapa e declara seu objeto MarkerClusterer:

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

    Onde multiChoice () é SUA função (ainda a ser escrita) para abrir uma InfoWindow com uma lista de opções para selecionar. Observe que o objeto markerClusterer é passado para sua função, porque você precisará disso para determinar quantos marcadores existem naquele cluster. Por exemplo:

    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;
    }
Inácio
fonte
17

Eu usei isso junto com o jQuery e ele faz o trabalho:

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);
Steve Graham
fonte
Thx funciona perfeitamente para mim :) Exatamente o que eu estava pesquisando desde horas: p
Jicao
Solução bonita e elegante. Obrigado!
Concept211
Solução perfeita compensando um pouco! É possível, passarmos o mouse sobre o marcador e ele exibir vários marcadores
Intekhab Khan
14

Expandindo a resposta de Chaoley , implementei uma função que, dada uma lista de locais (objetos com lnge latpropriedades) cujas coordenadas são exatamente as mesmas, os afasta um pouco de sua localização original (modificando os objetos no local). Em seguida, eles formam um belo círculo em torno do ponto central.

Descobri que, para minha latitude (52deg Norte), 0,0003 graus de raio de círculo funcionam melhor e que você deve compensar a diferença entre graus de latitude e longitude quando convertidos em quilômetros. Você pode encontrar conversões aproximadas para sua latitude aqui .

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;
    }
};
DzinX
fonte
1
DzinX, passei as últimas 4 horas tentando descobrir como posso implementar seu código em meu código sem sucesso. Cheguei à conclusão de que sou péssimo nisso e realmente aprecio sua ajuda. Aqui é onde estou tentando inserir o código: pastebin.com/5qYbvybm - QUALQUER AJUDA seria apreciada! Obrigado!
WebMW
WebMW: Depois de extrair os marcadores do XML, você deve primeiro agrupá-los em listas para que cada lista contenha marcadores apontando para exatamente o mesmo local. Em seguida, em cada lista que contém mais de um elemento, execute correctLocList (). Somente depois de fazer isso, crie objetos google.maps.Marker.
DzinX
Não está funcionando .... Apenas muda meus lat longs de 37.68625400000000000,-75.71669800000000000para 37.686254,-75.716698também, que é o mesmo para todos os valores ... M esperando algo como ... 37.68625400000000000,-75.71669800000000000para 37.6862540000001234,-75.7166980000001234 37.6862540000005678,-75.7166980000005678..etc .. Eu também tentei mudar meu ângulo.
Premshankar Tiwari
Hummm, vamos cavar um pouco e torcer para que o @DzinX ainda esteja na área. Você pode, companheiro (ou qualquer outra pessoa) explicar como você obtém todos esses números? Lng_radius, etc? Muito obrigado. :)
Vae
1
@Vae: O raio é o tamanho que você deseja que o círculo seja. Você quer que o círculo pareça redondo em pixels, então você precisa saber, em sua localização, qual é a proporção de 1 grau de latitude para 1 grau de longitude (em pixels no mapa). Você pode encontrar isso em alguns sites ou apenas experimentar até obter o número certo. O ângulo é a distância do primeiro pino à direita do topo.
DzinX
11

A resposta mais excelente do @Ignatius, atualizada para funcionar com a v2.0.7 do MarkerClustererPlus.

  1. Adicione um método de clique de protótipo na classe MarkerClusterer, assim - vamos substituir isso mais tarde na função initialize () do mapa:

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
  2. Na classe ClusterIcon, adicione o seguinte código APÓS o acionador 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. Então, em sua função initialize () onde você inicializa o mapa e declara seu objeto MarkerClusterer:

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

    Onde multiChoice () é SUA função (ainda a ser escrita) para abrir uma InfoWindow com uma lista de opções para selecionar. Observe que o objeto markerClusterer é passado para sua função, porque você precisará disso para determinar quantos marcadores existem naquele cluster. Por exemplo:

    function multiChoice(clickedCluster) {
      if (clickedCluster.getMarkers().length > 1)
      {
        // var markers = clickedCluster.getMarkers();
        // do something creative!
        return false;
      }
      return true;
    };
Nathan Colgate
fonte
1
Obrigado, isso funciona perfeitamente! Quanto ao seu maxZoomproblema, acho que é apenas um problema se não houver maxZoom definido ou o maxZoom para o cluster for maior que o zoom para o mapa. Substituí seu código por este snippet e parece funcionar muito bem:var maxZoom = mc.getMaxZoom(); if (null === maxZoom || maxZoom > mc.getMap().maxZoom) { maxZoom = mc.getMap().maxZoom; if (null === maxZoom) { maxZoom = 15; } }
Pascal
Eu sei que isso é muito antigo, mas estou tentando usar essa solução. Eu o tenho funcionando, mas no caso de mostrar uma janela de informações com os dados do cluster ... como faço para obter a posição do marcador do cluster que foi clicado em primeiro lugar para que eu possa mostrar a janela de informações? markers é minha matriz de dados ... mas como posso mostrar a janela de informações no ícone / marcador de cluster?
user756659
@ user756659 no multiChoice, você obtém lat / lng via: clickedCluster.center_.lat()/clickedCluster.center_.lng()
Jens Kohl
1
@Pascal e com um pouco de ofuscação acabamos com isso, o que poderia funcionar, mas como diz o tao de python. "contagens de legibilidade". var maxZoom = Math.min (mc.getMaxZoom () || 15, mc.getMap (). maxZoom || 15);
Nande de
6

As respostas acima são mais elegantes, mas descobri uma maneira rápida e suja que realmente funciona incrivelmente bem. Você pode vê-lo em ação em www.buildinglit.com

Tudo o que fiz foi adicionar um deslocamento aleatório à latitude e longditude à minha página genxml.php para que retornasse resultados ligeiramente diferentes a cada vez com deslocamento cada vez que o mapa fosse criado com marcadores. Isso soa como um hack, mas na realidade você só precisa que os marcadores se movam um leve empurrão em uma direção aleatória para serem clicáveis ​​no mapa se estiverem sobrepostos. Na verdade funciona muito bem, eu diria melhor do que o método da aranha, porque quem quer lidar com essa complexidade e tê-los surgindo em todos os lugares. Você só deseja poder selecionar o marcador. Empurrar aleatoriamente funciona perfeitamente.

Aqui está um exemplo da criação do nó de iteração da instrução while em meu 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);

Observe que em latitude e longitude existe o deslocamento +. das 2 variáveis ​​acima. Tive que dividir aleatoriamente por 0,1000 por 10000000 para obter um decimal que fosse aleatoriamente pequeno o suficiente para apenas mover os marcadores. Sinta-se à vontade para mexer nessa variável para obter uma que seja mais precisa para as suas necessidades.

Matthew Fox
fonte
Legal! este é o hack sujo que achei muito útil, não preciso mexer no código de API do Google Maps
CuongDC
3

Para situações em que há vários serviços no mesmo prédio, você pode deslocar os marcadores um pouco, (digamos em 0,001 grau), em um raio do ponto real. Isso também deve produzir um bom efeito visual.

Chaoley
fonte
3

Esta é mais uma solução provisória 'rápida e suja' semelhante à sugerida por Matthew Fox, desta vez usando JavaScript.

Em JavaScript, você pode apenas compensar a latitude e a longitude de todos os seus locais adicionando um pequeno deslocamento aleatório a ambos, por exemplo

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

(Descobri que dividir por 25000 dá separação suficiente, mas não move o marcador significativamente do local exato, por exemplo, um endereço específico)

Isso faz um trabalho razoavelmente bom em compensá-los um do outro, mas somente depois de você ter ampliado de perto. Quando diminuído o zoom, ainda não ficará claro se existem várias opções para o local.

Chris Halcrow
fonte
1
Eu gosto disso. Se você não precisa de marcadores representativos com precisão, apenas diminua 25.000 para 250 ou 25. Solução simples e rápida.
Fid
2

Confira Marker Clusterer para V3 - esta biblioteca agrupa pontos próximos em um marcador de grupo. O mapa aumenta o zoom quando os clusters são clicados. Eu imagino que quando ampliado você ainda terá o mesmo problema com marcadores no mesmo local.

Periquito
fonte
Sim, dei uma olhada naquela pequena biblioteca útil, mas ainda não parece resolver o problema. Estou fazendo um localizador de serviço para uma empresa que às vezes tem mais de um serviço no mesmo bloco de escritórios e, portanto, exatamente os mesmos geo-coords. Posso mostrar os diferentes serviços em uma janela de informações, mas essa não parece ser a solução mais elegante ...
Louzoid
@Louzoid Marker Clusterer não resolve o problema como acabei de descobrir. Estou com o mesmo problema, tenho várias empresas dentro de um prédio e portanto só mostra 1 marcador.
Rob
1

Atualizado para funcionar com 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
Thoshino
fonte
Esta abordagem requer que o usuário clique no ícone do cluster e não cobrirá os casos de uso em que a navegação é feita com o controle deslizante de zoom ou rolagem do mouse. Alguma ideia de como lidar com esses problemas?
Remi Sture
1

Eu gosto de soluções simples, então aqui está a minha. Em vez de modificar o lib, o que tornaria mais difícil mantê-lo. você pode simplesmente assistir ao evento assim

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

então você pode gerenciar isso em

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

i, ou seja, usei o bootstrap para mostrar um painel com uma lista. que considero muito mais confortável e útil do que caça-aranha em lugares "lotados". (se você estiver usando um clusterer, as chances são de que você acabará com colisões depois de fazer spiderfy). você pode verificar o zoom lá também.

btw. Acabei de encontrar o folheto e parece funcionar muito melhor, o cluster AND spiderfy funciona de forma muito fluida http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html e é de código aberto.

Nande
fonte
0

Expandindo as respostas fornecidas acima, apenas certifique-se de definir a opção maxZoom ao inicializar o objeto de mapa.

HosseinSafy
fonte
0

Dar deslocamento fará com que os marcadores fiquem distantes quando o usuário aumentar o zoom no máximo Então eu encontrei uma maneira de conseguir isso. esta pode não ser a maneira correta, mas funcionou muito bem.

// 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
}

defina o zIndex do marcador de forma que você veja o ícone do marcador que deseja ver no topo, caso contrário, os marcadores serão animados como troca automática. quando o usuário toca no marcador, segure o zIndex para trazer o marcador para cima usando zIndex Swap.

Satheesh G
fonte
0

Como escapar impune .. [Swift]

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

Um novo marcador está para ser criado? verificar clusterArraye manipular o deslocamento

 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)
        }


    }

resultado

insira a descrição da imagem aqui

Alexandros
fonte
0

Somando-se à resposta genial de Matthew Fox, adicionei um pequeno deslocamento aleatório para cada lat e lng ao definir o objeto marcador. Por exemplo:

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;
}
buscando Stillness
fonte
-2

Estendendo as respostas acima, quando você juntou strings, não adicionou / subtraiu a posição (por exemplo, "37.12340-0.00069"), converta sua latitude / longitude original em flutuantes, por exemplo, usando parseFloat (), então adicione ou subtraia correções.

vip
fonte