API de Google Maps V3: varios marcadores en el mismo lugar exacto


115

Un poco atascado en este. Estoy recuperando una lista de coordenadas geográficas a través de JSON y las coloco en un mapa de Google. Todo funciona bien, excepto en el caso de que tenga dos o más marcadores en el mismo lugar exacto. La API solo muestra 1 marcador: el superior. Esto es lo suficientemente justo, supongo, pero me gustaría encontrar una manera de mostrarlos todos de alguna manera.

Busqué en Google y encontré algunas soluciones, pero en su mayoría parecen ser para V2 de la API o simplemente no tan buenas. Idealmente, me gustaría una solución en la que haga clic en algún tipo de marcador de grupo y luego muestre los marcadores agrupados alrededor del lugar en el que están todos.

¿Alguien tuvo este problema o similar y le gustaría compartir una solución?

Respuestas:


116

Eche un vistazo a OverlappingMarkerSpiderfier .
Hay una página de demostración, pero no muestran marcadores que estén exactamente en el mismo lugar, solo algunos que están muy juntos.

Pero un ejemplo de la vida real con marcadores en el mismo lugar exacto se puede ver en http://www.ejw.de/ejw-vor-ort/ (desplácese hacia abajo para ver el mapa y haga clic en algunos marcadores para ver el efecto araña ).

Esa parece ser la solución perfecta para su problema.


Esto es asombroso y satisface perfectamente la necesidad que queda de la biblioteca del clúster, que no maneja marcadores con la misma latitud / longitud exacta.
Justin

Si se usa OverlappingMarkerSpiderfieren combinación con MarkerClusterer, una buena opción es establecer la maxZoomopción en el constructor del clúster. Esto "desagrupa" los marcadores después del nivel de zoom especificado.
Diederik

@Tad No sé si usted es el desarrollador de ese sitio web, pero quería hacerle saber que el mapa de ejw.de está roto. Recibo un error de JS.
Alex

Revisé la demostración propuesta, pero parece muerta. Después de investigar más, encontré este ejemplo sobre cómo integrar grupos de marcadores y Spiderifier. No hay demostración en vivo, solo clona y sigue el archivo Léame. github.com/yagoferrer/markerclusterer-plus-spiderfier-example
Sax

34

Desplazar los marcadores no es una solución real si están ubicados en el mismo edificio. Lo que podría querer hacer es modificar el marcadorclusterer.js así:

  1. Agregue un método de clic de prototipo en la clase MarkerClusterer, así; lo anularemos más adelante en la función initialize () del mapa:

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
  2. En la clase ClusterIcon, agregue el siguiente código DESPUÉS del activador 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. Luego, en su función initialize () donde inicializa el mapa y declara su objeto MarkerClusterer:

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

    Donde multiChoice () es SU función (aún por escribir) para abrir una ventana de información con una lista de opciones para seleccionar. Tenga en cuenta que el objeto markerClusterer se pasa a su función, porque lo necesitará para determinar cuántos marcadores hay en ese clúster. Por ejemplo:

    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

Usé esto junto con jQuery y hace el trabajo:

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 funciona perfectamente para mí :) Justo lo que estaba buscando desde horas: p
Jicao

Solución hermosa y elegante. ¡Gracias!
Concept211

¡Solución perfecta compensando un poco! ¿Es posible, colocamos el cursor sobre el marcador y luego muestra varios marcadores
Intekhab Khan

14

Ampliando la respuesta de Chaoley , implementé una función que, dada una lista de ubicaciones (objetos con lngylat propiedades) cuyas coordenadas son exactamente iguales, los aleja un poco de su ubicación original (modificando objetos en su lugar). Luego forman un bonito círculo alrededor del punto central.

Descubrí que, para mi latitud (52 grados norte), 0,0003 grados de radio de círculo funcionan mejor, y que debes compensar la diferencia entre los grados de latitud y longitud al convertirlos a kilómetros. Puede encontrar conversiones aproximadas para su latitud aquí .

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, he pasado las últimas 4 horas tratando de averiguar cómo puedo implementar su código en mi código sin éxito. He llegado a la conclusión de que apesto en esto y realmente agradecería su ayuda. Aquí es donde estoy intentando conectar el código: pastebin.com/5qYbvybm - ¡CUALQUIER AYUDA sería apreciada! ¡Gracias!
WebMW

WebMW: después de extraer los marcadores de XML, primero debe agruparlos en listas para que cada lista contenga marcadores que apunten exactamente a la misma ubicación. Luego, en cada lista que contenga más de un elemento, ejecute correctLocList (). Solo después de hacerlo, cree objetos google.maps.Marker.
DzinX

No está funcionando ... Simplemente cambia mi lat longs de 37.68625400000000000,-75.71669800000000000a 37.686254,-75.716698también, que es el mismo para todos los valores ... M esperando algo como ... 37.68625400000000000,-75.71669800000000000to 37.6862540000001234,-75.7166980000001234 37.6862540000005678,-75.7166980000005678..etc .. También intenté cambiar mi ángulo.
Premshankar Tiwari

Hummm, profundicemos un poco y esperemos que @DzinX todavía esté en el área. ¿Puede aparearse (o cualquier otra persona) explicar cómo obtiene todos esos números? Lng_radius, etc? Muchas gracias. :)
Vae

1
@Vae: El radio es el tamaño que desea que tenga el círculo. Desea que el círculo se vea redondo en píxeles, por lo que necesita saber, en su ubicación, cuál es la proporción de 1 grado de latitud a 1 grado de longitud (en píxeles en el mapa). Puede encontrar esto en algunos sitios web o simplemente experimentar hasta obtener el número correcto. El ángulo es cuánto está el primer pin a la derecha desde la parte superior.
DzinX

11

La respuesta más excelente de @Ignatius, actualizada para funcionar con la v2.0.7 de MarkerClustererPlus.

  1. Agregue un método de clic de prototipo en la clase MarkerClusterer, así; lo anularemos más adelante en la función initialize () del mapa:

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
  2. En la clase ClusterIcon, agregue el siguiente código DESPUÉS del activador 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. Luego, en su función initialize () donde inicializa el mapa y declara su objeto MarkerClusterer:

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

    Donde multiChoice () es SU función (aún por escribir) para abrir una ventana de información con una lista de opciones para seleccionar. Tenga en cuenta que el objeto markerClusterer se pasa a su función, porque lo necesitará para determinar cuántos marcadores hay en ese clúster. Por ejemplo:

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

1
¡Gracias, esto funciona perfectamente! En cuanto a su maxZoomproblema, creo que esto solo es un problema si no hay un conjunto de maxZoom o el maxZoom para el clúster es mayor que el zoom para el mapa. Reemplacé su código fijo con este fragmento y parece funcionar bien:var maxZoom = mc.getMaxZoom(); if (null === maxZoom || maxZoom > mc.getMap().maxZoom) { maxZoom = mc.getMap().maxZoom; if (null === maxZoom) { maxZoom = 15; } }
Pascal

Sé que esto es muy antiguo, pero estoy tratando de usar esta solución. Lo tengo funcionando, pero en el caso de mostrar una ventana de información con los datos del clúster ... ¿cómo obtengo la posición del marcador del clúster en el que se hizo clic en primer lugar para poder mostrar la ventana de información? marcadores es mi matriz de datos ... pero ¿cómo puedo mostrar la ventana de información en el icono / marcador del clúster?
user756659

@ user756659 en multiChoice obtienes lat / lng a través de: clickedCluster.center_.lat()/clickedCluster.center_.lng()
Jens Kohl

1
@Pascal y con un poco de ofuscación terminamos con esto, que podría funcionar, pero como dice el tao de python. "la legibilidad cuenta". var maxZoom = Math.min (mc.getMaxZoom () || 15, mc.getMap (). maxZoom || 15);
Nande

6

Las respuestas anteriores son más elegantes, pero encontré una forma rápida y sucia que realmente funciona realmente increíblemente bien. Puede verlo en acción en www.buildinglit.com

Todo lo que hice fue agregar un desplazamiento aleatorio a la latitud y longitud a mi página genxml.php para que devuelva resultados ligeramente diferentes cada vez con desplazamiento cada vez que se crea el mapa con marcadores. Esto suena como un truco, pero en realidad solo necesita que los marcadores se muevan un ligero empujón en una dirección aleatoria para que se pueda hacer clic en el mapa si se superponen. De hecho, funciona muy bien, yo diría que mejor que el método araña porque ¿quién quiere lidiar con esa complejidad y hacer que salten por todas partes? Solo desea poder seleccionar el marcador. Empujarlo al azar funciona perfecto.

Aquí hay un ejemplo de la creación del nodo de iteración de la declaración while en mi 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 debajo de lat y long está el desplazamiento +. de las 2 variables anteriores. Tuve que dividir al azar por 0,1000 por 10000000 para obtener un decimal que fuera lo suficientemente pequeño al azar como para mover apenas los marcadores. Siéntase libre de jugar con esa variable para obtener una que sea más precisa para sus necesidades.


¡Frio! este es el truco sucio que encontré muy útil, no es necesario jugar con el código de API de Google Maps
CuongDC

3

Para situaciones en las que hay varios servicios en el mismo edificio, puede compensar los marcadores solo un poco (por ejemplo, en 0,001 grados) en un radio desde el punto real. Esto también debería producir un efecto visual agradable.


3

Esta es más una solución provisional "rápida y sucia" similar a la que sugiere Matthew Fox, esta vez usando JavaScript.

En JavaScript, puede compensar la latitud y la longitud de todas sus ubicaciones agregando una pequeña compensación aleatoria a ambas, por ejemplo

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

(Encontré que dividir por 25000 da suficiente separación pero no mueve el marcador significativamente de la ubicación exacta, por ejemplo, una dirección específica)

Esto hace un trabajo razonablemente bueno al compensarlos entre sí, pero solo después de haber acercado el zoom de cerca. Cuando se aleja, aún no estará claro que hay múltiples opciones para la ubicación.


1
Me gusta esto. Si no necesita marcadores representativos con precisión, simplemente reduzca 25000 a 250 o 25. Solución simple y rápida.
Fid

2

Echa un vistazo a Marker Clusterer para V3: esta biblioteca agrupa los puntos cercanos en un marcador de grupo. El mapa se acerca cuando se hace clic en los grupos. Sin embargo, me imagino que cuando se amplía el zoom, todavía tendrá el mismo problema con los marcadores en el mismo lugar.


Sí, eché un vistazo a esa pequeña y práctica biblioteca, pero todavía no parece solucionar el problema. Estoy haciendo un localizador de servicios para una empresa que a veces tiene más de un servicio en el mismo bloque de oficinas y, por lo tanto, exactamente las mismas coordenadas geográficas. Puedo mostrar los diferentes servicios en una ventana de información, pero esta no parece ser la solución más elegante ...
Louzoid

@Louzoid Marker Clusterer no supera el problema como acabo de descubrir. Tengo el mismo problema, tengo varias empresas dentro de un edificio y, por lo tanto, solo muestra 1 marcador.
Rob

1

Actualizado para trabajar 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

Este enfoque requiere que el usuario haga clic en el icono del clúster y no cubrirá los casos de uso en los que la navegación se realiza con el control deslizante de zoom o el desplazamiento del mouse. ¿Alguna idea de cómo abordar esos problemas?
Remi Sture

1

Me gustan las soluciones simples, así que aquí está la mía. En lugar de modificar la biblioteca, lo que haría más difícil mantener. simplemente puedes ver el evento así

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

entonces puedes administrarlo en

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

Yo, es decir, usé bootstrap para mostrar un panel con una lista. que encuentro mucho más cómodo y utilizable que hacer spiderfying en lugares "llenos de gente". (si está utilizando un clusterer, es probable que termine con colisiones una vez que se arme). también puedes comprobar el zoom allí.

por cierto. Acabo de encontrar un folleto y parece funcionar mucho mejor, el clúster Y spiderfy funciona de manera muy fluida http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html y es de código abierto.



0

Ampliando las respuestas dadas anteriormente, solo asegúrese de configurar la opción maxZoom al inicializar el objeto del mapa.


0

Dar un desplazamiento alejará los marcadores cuando el usuario se acerque al máximo. Así que encontré una manera de lograrlo. Puede que esta no sea la forma adecuada, pero funcionó muy bien.

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

configure el zIndex del marcador para que vea el icono del marcador que desea ver en la parte superior; de lo contrario, animará los marcadores como un intercambio automático. cuando el usuario toca el marcador, maneja el zIndex para traer el marcador en la parte superior usando zIndex Swap.


0

Cómo salirse con la suya ... [Swift]

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

¿Está a punto de crearse un nuevo marcador? comprobar clusterArrayy manipular su desplazamiento

 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

ingrese la descripción de la imagen aquí


0

Agregando a la astuta respuesta del genio de Matthew Fox, he agregado un pequeño desplazamiento aleatorio a cada lat y lng al configurar el objeto marcador. Por ejemplo:

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

Ampliando las respuestas anteriores, cuando tenga cadenas unidas, no una posición agregada / restada (por ejemplo, "37.12340-0.00069"), convierta su latitud / longitud original en flotantes, por ejemplo, usando parseFloat (), luego agregue o reste correcciones.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.