web-dev-qa-db-fra.com

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

Peu coincé sur celui-ci. Je récupère une liste de coordonnées géographiques via JSON et les affiche sur une carte google. Tout fonctionne bien, sauf dans le cas où j'ai deux marqueurs ou plus au même endroit. L'API n'affiche qu'un seul marqueur - le premier. 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 surtout être pour la V2 de l'API ou tout simplement pas très bien. Idéalement, j'aimerais une solution dans laquelle vous cliquez sur une sorte de marqueur de groupe et qui affiche ensuite les marqueurs regroupés autour de l'endroit où ils se trouvent tous.

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

109
Louzoid

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

Mais on peut voir un exemple concret avec des marqueurs exactement au même endroit 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 araignée).

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

111
Tad

Compenser les marqueurs n'est pas une solution réelle 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 ci-dessous - nous remplacerons cela plus tard dans la fonction map initialize ():

    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 (), 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 afficher une fenêtre InfoWindow avec une liste d'options à sélectionner. Notez que l'objet markerClusterer est transmis à 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;
    }
    
32
Ignatius

Je l'ai utilisé aux côtés de 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);
15
Steve Graham

En développant réponse de Chaoley , j'ai implémenté une fonction qui, étant donnée une liste d'emplacements (objets avec les propriétés lng et lat), dont les coordonnées sont exactement les mêmes, les déplace loin de leur emplacement d'origine (modification d'objets en place). Ils forment alors un cercle de Nice autour du point central.

J'ai trouvé que, pour ma latitude (52 degrés Nord), 0,0003 degré de rayon de cercle fonctionnent mieux et que vous devez compenser la différence entre les degrés de latitude et de longitude 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;
    }
};
14
DzinX

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

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

    // 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 (), 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 afficher une fenêtre InfoWindow avec une liste d'options à sélectionner. Notez que l'objet markerClusterer est transmis à 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;
    };
    
9
Nathan Colgate

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

J'ai simplement ajouté un décalage aléatoire à la latitude et à longditude de 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 bidouillage, mais en réalité, il suffit que les marqueurs déplacent un léger mouvement de va-et-vient dans une direction aléatoire pour qu'ils soient cliquables sur la carte s'ils se chevauchent. En fait, cela fonctionne vraiment bien, je dirais mieux que la méthode de l'araignée, car qui veut gérer cette complexité et la faire ressortir partout. Vous voulez juste pouvoir sélectionner le marqueur. Le pousser au hasard fonctionne parfaitement.

Voici un exemple de création de noeud d'itération d'instruction while dans le fichier 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);

Avis sous lat et long il y a le décalage +. parmi les 2 variables ci-dessus. Je devais diviser au hasard 0,1 000 sur 10 000 000 pour obtenir une décimale suffisamment petite au hasard pour déplacer à peine les balises. N'hésitez pas à bricoler cette variable pour en obtenir une plus précise pour vos besoins.

4
Matthew Fox

Dans les cas où il existe plusieurs services dans le même bâtiment, vous pouvez légèrement décaler les repères (disons de 0,001 degré) dans un rayon du point réel. Cela devrait également produire un bel effet visuel.

3
Chaoley

Il s’agit plutôt d’une solution "rapide et sale", semblable à celle proposée par Matthew Fox, qui utilise JavaScript cette fois.

En JavaScript, vous pouvez simplement compenser la latitude et la longitude de toutes vos positions en ajoutant un petit décalage aléatoire à la fois par exemple

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

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

Cela les compense assez bien, mais seulement après avoir effectué un zoom avant étroit. Lorsque vous effectuez un zoom arrière, il ne sera toujours pas clair qu'il existe plusieurs options pour l'emplacement.

2
Chris Halcrow

Vérifiez Marker Clusterer pour V3 - cette bibliothèque regroupe les points voisins dans un marqueur de groupe. La carte effectue un zoom avant lorsque les clusters sont cliqués. J'imagine que, même en zoomant, vous auriez toujours le même problème avec les marqueurs au même endroit.

2
Budgie

Mise à 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
1
thoshino

J'aime les solutions simples alors voici la mienne. Au lieu de modifier la bibliothèque, ce qui rendrait la tâche plus difficile. 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();

c’est-à-dire utilisé bootstrap pour afficher un panneau avec une liste. Ce que je trouve beaucoup plus confortable et utilisable que de faire de la spider sur des lieux "bondés" (si vous utilisez un clusterer, il est fort probable que vous le fassiez). finissez avec des collisions une fois que vous avez fait spiderfy), vous pouvez aussi vérifier le zoom.

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

1
Nande

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érifie clusterArray et manipule son 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)
        }


    }

résultat

enter image description here

0
Alexandros

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
HosseinSafy

Vérifiez ceci: https://github.com/plank/MarkerClusterer

Il s'agit du MarkerCluster modifié pour avoir un infoWindow dans un marqueur de cluster, lorsque vous avez plusieurs marqueurs dans la même position.

Vous pouvez voir comment cela fonctionne ici: http://culturedays.ca/fr/2013-activities

0
Marc Canals Giraut

Ajoutant à la réponse sournoise de génie 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;
}
0
seekingStillness

Donner un décalage éloignera les repères lorsque l'utilisateur effectuera un zoom avant. J'ai donc trouvé un moyen d'y parvenir. ce n'est peut-être pas un bon moyen 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 de manière à ce que l’icône du marqueur que vous souhaitez voir apparaisse en haut, sinon elle animera les marqueurs comme une permutation automatique. Lorsque l'utilisateur appuie sur le marqueur, gérez le zIndex pour le placer au-dessus à l'aide de zIndex Swap.

0
Satheesh G