Google Maps API V3 - plusieurs marqueurs exactement au même endroit


115

Bit coincé sur celui-ci. Je récupère une liste de coordonnées géographiques via JSON et je les affiche sur une carte Google. Tout fonctionne bien sauf dans le cas où j'ai deux marqueurs ou plus exactement au même endroit. L'API n'affiche qu'un seul marqueur - celui du haut. C'est assez juste, je suppose, mais j'aimerais trouver un moyen de les afficher tous d'une manière ou d'une autre.

J'ai cherché sur Google et trouvé quelques solutions, mais elles semblent principalement être pour la version V2 de l'API ou tout simplement pas très bonnes. Idéalement, j'aimerais une solution où vous cliquez sur une sorte de marqueur de groupe et qui montre ensuite les marqueurs regroupés autour de l'endroit où ils se trouvent tous.

Quelqu'un a eu ce problème ou similaire et voudrait partager une solution?

Réponses:


116

Jetez un œil à OverlappingMarkerSpiderfier .
Il y a une page de démonstration, mais ils ne montrent pas les marqueurs qui sont exactement au même endroit, seulement certains qui sont très proches les uns des autres.

Mais un exemple réel avec des marqueurs exactement au même endroit peut être vu sur http://www.ejw.de/ejw-vor-ort/ (faites défiler la carte vers le bas et cliquez sur quelques marqueurs pour voir l'effet d'araignée ).

Cela semble être la solution parfaite à votre problème.


C'est génial et répond parfaitement au besoin laissé par la bibliothèque de cluster, qui ne gère pas les marqueurs avec la même lat / long exacte.
Justin le

Si vous utilisez OverlappingMarkerSpiderfieren combinaison avec MarkerClusterer, une bonne option consiste à définir l' maxZoomoption sur le constructeur de cluster. Ceci "décompose" les marqueurs après le niveau de zoom spécifié.
Diederik

@Tad Je ne sais pas si vous êtes le développeur de ce site Web, mais je voulais vous faire savoir que la carte sur ejw.de est cassée. J'obtiens une erreur JS.
Alex

J'ai vérifié la démo proposée, mais elle semble morte. Après de plus amples recherches, j'ai trouvé cet exemple sur la façon d'intégrer des clusters de marqueurs et Spiderifier. Pas de démo en direct, mais juste cloner et suivre le readme. github.com/yagoferrer/markerclusterer-plus-spiderfier-example
Sax

34

Décaler les marqueurs n'est pas une vraie solution s'ils sont situés dans le même bâtiment. Ce que vous voudrez peut-être faire est de modifier le markerclusterer.js comme ceci:

  1. Ajoutez une méthode de clic prototype dans la classe MarkerClusterer, comme ceci - nous remplacerons cela plus tard dans la fonction initialize () de la carte:

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    
  2. Dans la classe ClusterIcon, ajoutez le code suivant APRÈS le déclencheur 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. Ensuite, dans votre fonction initialize () où vous initialisez la carte et déclarez votre objet MarkerClusterer:

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

    Où multiChoice () est VOTRE (encore à écrire) fonction pour faire apparaître une InfoWindow avec une liste d'options à sélectionner. Notez que l'objet markerClusterer est passé à votre fonction, car vous en aurez besoin pour déterminer le nombre de marqueurs dans ce cluster. Par exemple:

    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

J'ai utilisé ceci avec jQuery et cela fait le travail:

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 fonctionne parfaitement pour moi :) Juste ce que je cherchais depuis des heures: p
Jicao

Belle et élégante solution. Je vous remercie!
Concept211

Solution parfaite compensant un peu! Est-ce possible, nous survolons le marqueur et ensuite il affichera plusieurs marqueurs
Intekhab Khan

14

En développant la réponse de Chaoley , j'ai implémenté une fonction qui, étant donné une liste d'emplacements (objets avec lnget latpropriétés) dont les coordonnées sont exactement les mêmes, les éloigne un peu de leur emplacement d'origine (modification des objets en place). Ils forment alors un joli cercle autour du point central.

J'ai trouvé que, pour ma latitude (52deg Nord), 0,0003 degrés de rayon de cercle fonctionnent le mieux, et que vous devez compenser la différence entre les degrés de latitude et de longitude lorsqu'ils sont convertis en kilomètres. Vous pouvez trouver des conversions approximatives pour votre latitude ici .

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, j'ai passé les 4 dernières heures à essayer de comprendre comment je peux implémenter votre code dans mon code en vain. J'en suis venu à la conclusion que je suis nul et j'apprécierais vraiment votre aide. Voici où j'essaye de brancher le code: pastebin.com/5qYbvybm - TOUTE AIDE serait appréciée! Merci!
WebMW

WebMW: après avoir extrait les marqueurs de XML, vous devez d'abord les regrouper en listes afin que chaque liste contienne des marqueurs pointant exactement vers le même emplacement. Ensuite, sur chaque liste qui contient plus d'un élément, exécutez correctLocList (). Ce n'est qu'après cela que vous créez des objets google.maps.Marker.
DzinX

Ça ne marche pas .... Il change juste mes lat longs de 37.68625400000000000,-75.71669800000000000à 37.686254,-75.716698aussi ce qui est le même pour toutes les valeurs ... M m'attendant à quelque chose comme ... 37.68625400000000000,-75.71669800000000000à 37.6862540000001234,-75.7166980000001234 37.6862540000005678,-75.7166980000005678..etc .. J'ai aussi essayé de changer mon angle.
Premshankar Tiwari

Hummm, creusons un peu et espérons que @DzinX est toujours dans la région. Pouvez-vous m'expliquer (ou quelqu'un d'autre) comment vous obtenez tous ces nombres? Lng_radius, etc? Merci beaucoup. :)
Vae

1
@Vae: Le rayon correspond à la taille souhaitée du cercle. Vous voulez que le cercle ait l'air rond en pixels, vous devez donc savoir, à votre emplacement, quelle est la proportion de 1 degré de latitude pour 1 degré de longitude (en pixels sur la carte). Vous pouvez le trouver sur certains sites Web ou simplement expérimenter jusqu'à ce que vous obteniez le bon numéro. L'angle correspond à la distance entre la première broche et la droite du haut.
DzinX

11

@Ignatius la réponse la plus excellente, mise à jour pour fonctionner avec la v2.0.7 de MarkerClustererPlus.

  1. Ajoutez une méthode de clic prototype dans la classe MarkerClusterer, comme ceci - nous remplacerons cela plus tard dans la fonction initialize () de la carte:

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
  2. Dans la classe ClusterIcon, ajoutez le code suivant APRÈS le déclencheur 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. Ensuite, dans votre fonction initialize () où vous initialisez la carte et déclarez votre objet MarkerClusterer:

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

    Où multiChoice () est VOTRE (encore à écrire) fonction pour faire apparaître une InfoWindow avec une liste d'options à sélectionner. Notez que l'objet markerClusterer est passé à votre fonction, car vous en aurez besoin pour déterminer le nombre de marqueurs dans ce cluster. Par exemple:

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

1
Merci, cela fonctionne parfaitement! En ce qui concerne votre maxZoomproblème, je pense que ce n'est un problème que s'il n'y a pas de jeu maxZoom ou si maxZoom pour le cluster est supérieur au zoom de la carte. J'ai remplacé votre codé en dur par cet extrait et il semble bien fonctionner:var maxZoom = mc.getMaxZoom(); if (null === maxZoom || maxZoom > mc.getMap().maxZoom) { maxZoom = mc.getMap().maxZoom; if (null === maxZoom) { maxZoom = 15; } }
Pascal

Je sais que c'est vraiment vieux, mais j'essaye d'utiliser cette solution. Je le fais fonctionner, mais dans le cas de l'affichage d'une infowindow avec les données du cluster ... comment obtenir la position du marqueur du cluster sur lequel on a cliqué en premier lieu afin que je puisse afficher l'infowindow? markers est mon tableau de données ... mais comment puis-je afficher l'infowindow sur l'icône / le marqueur de cluster?
user756659

@ user756659 dans multiChoice vous obtenez lat / lng via: clickedCluster.center_.lat()/clickedCluster.center_.lng()
Jens Kohl

1
@Pascal et avec un peu d'obscurcissement, nous nous retrouvons avec ceci, qui pourrait fonctionner, mais comme le dit le tao de python. "la lisibilité compte". var maxZoom = Math.min (mc.getMaxZoom () || 15, mc.getMap (). maxZoom || 15);
Nande

6

Les réponses ci-dessus sont plus élégantes, mais j'ai trouvé un moyen rapide et sale qui fonctionne vraiment très bien. Vous pouvez le voir en action sur www.buildinglit.com

Tout ce que j'ai fait a été d'ajouter un décalage aléatoire à la latitude et à la longueur à ma page genxml.php afin qu'elle renvoie des résultats légèrement différents à chaque fois avec un décalage chaque fois que la carte est créée avec des marqueurs. Cela ressemble à un hack, mais en réalité, vous n'avez besoin que des marqueurs pour déplacer un léger coup de pouce dans une direction aléatoire pour qu'ils soient cliquables sur la carte s'ils se chevauchent. Cela fonctionne vraiment très bien, je dirais mieux que la méthode de l'araignée, car qui veut faire face à cette complexité et les faire jaillir partout. Vous voulez simplement pouvoir sélectionner le marqueur. Le pousser au hasard fonctionne parfaitement.

Voici un exemple de création de nœud d'itération d'instruction while dans mon 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);

Remarquez sous lat et long, il y a le + offset. à partir des 2 variables ci-dessus. J'ai dû diviser au hasard par 0,1000 par 10000000 afin d'obtenir une décimale suffisamment petite au hasard pour déplacer à peine les marqueurs. N'hésitez pas à bricoler cette variable pour en obtenir une plus précise pour vos besoins.


Cool! c'est le sale hack que j'ai trouvé très utile, pas besoin de jouer avec le code api de Google Maps
CuongDC

3

Pour les situations où il y a plusieurs services dans le même bâtiment, vous pouvez décaler légèrement les marqueurs (par exemple de 0,001 degré) dans un rayon du point réel. Cela devrait également produire un bel effet visuel.


3

Il s'agit plus d'une solution provisoire «rapide et sale» similaire à celle suggérée par Matthew Fox, cette fois en utilisant JavaScript.

En JavaScript, vous pouvez simplement compenser la lat et la longueur de tous vos emplacements en ajoutant un petit décalage aléatoire aux deux, par exemple

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

(J'ai trouvé que la division par 25000 donne une séparation suffisante mais ne déplace pas le marqueur de manière significative de l'emplacement exact, par exemple une adresse spécifique)

Cela fait un assez bon travail pour les compenser les uns des autres, mais seulement après avoir zoomé de près. Lors d'un zoom arrière, il ne sera toujours pas clair qu'il existe plusieurs options pour l'emplacement.


1
J'aime ça. Si vous n'avez pas besoin de marqueurs représentatifs précis, abaissez simplement 25 000 à 250 ou 25. Solution simple et rapide.
Fid

2

Découvrez Marker Clusterer pour V3 - cette bibliothèque regroupe les points à proximité dans un marqueur de groupe. La carte effectue un zoom avant lorsque l'utilisateur clique sur les clusters. J'imagine que lorsque vous zoomez directement, vous aurez toujours le même problème avec les marqueurs au même endroit.


Oui, jetez un œil à cette petite bibliothèque pratique, mais elle ne semble toujours pas résoudre le problème. Je crée un localisateur de services pour une entreprise qui a parfois plus d'un service dans le même immeuble de bureaux et donc exactement les mêmes coordonnées géographiques. Je peux montrer les différents services dans une seule fenêtre d'information, mais cela ne semble tout simplement pas être la solution la plus élégante ...
Louzoid

@Louzoid Marker Clusterer ne résout pas le problème comme je viens de le découvrir. J'ai le même problème, j'ai plusieurs entreprises dans un même bâtiment et donc il n'affiche qu'un seul marqueur.
Rob

1

Mis à jour pour fonctionner avec 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

Cette approche nécessite que l'utilisateur clique sur l'icône du cluster et ne couvrira pas les cas d'utilisation où la navigation est effectuée avec le curseur de zoom ou le défilement de la souris. Avez-vous des idées pour résoudre ces problèmes?
Remi Sture

1

J'aime les solutions simples alors voici la mienne. Au lieu de modifier la lib, ce qui la rendrait plus difficile à maintenir. vous pouvez simplement regarder l'événement comme ça

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

alors vous pouvez le gérer sur

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

i, c'est-à-dire, utilisé bootstrap pour afficher un panneau avec une liste. ce que je trouve beaucoup plus confortable et utilisable que spiderfying sur des endroits "bondés". (si vous utilisez un clusterer, vous risquez de vous retrouver avec des collisions une fois que vous avez spiderfy). vous pouvez également vérifier le zoom.

btw. Je viens de trouver un dépliant et il semble fonctionner beaucoup mieux, le cluster AND spiderfy fonctionne très facilement http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html et il est open-source.



0

En développant les réponses données ci-dessus, assurez-vous simplement de définir l'option maxZoom lors de l'initialisation de l'objet de la carte.


0

Donner un décalage éloignera les marqueurs lorsque l'utilisateur zoomera au maximum. J'ai donc trouvé un moyen d'y parvenir. ce n'est peut-être pas une bonne méthode, mais cela a très bien fonctionné.

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

définissez le zIndex du marqueur afin que vous voyiez l'icône du marqueur que vous voulez voir en haut, sinon cela animera les marqueurs comme un échange automatique. lorsque l'utilisateur touche le marqueur, manipulez le zIndex pour amener le marqueur sur le dessus à l'aide de zIndex Swap.


0

Comment s'en sortir… [Swift]

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

Un nouveau marqueur est sur le point d'être créé? vérifier clusterArrayet manipuler son décalage

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


    }

résultat

entrez la description de l'image ici


0

En plus de la réponse géniale sournoise de Matthew Fox, j'ai ajouté un petit décalage aléatoire à chaque latitude et longitude lors de la définition de l'objet marqueur. Par exemple:

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

En étendant les réponses ci-dessus, lorsque vous avez des chaînes jointes, pas de position ajoutée / soustraite (par exemple "37.12340-0.00069"), convertissez votre lat / longitude d'origine en flottants, par exemple en utilisant parseFloat (), puis ajoutez ou soustrayez des corrections.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.