Google Maps API V3 - несколько маркеров на одном и том же месте

Бит застрял на этом. Я извлекаю список геокодеров через JSON и вывожу их на карту google. Все работает хорошо, за исключением случая, когда у меня есть два или более маркеров на одном и том же месте. API отображает только 1 маркер - верхний. Это справедливо, я полагаю, но хотел бы найти способ показать их как-то.

Я искал google и нашел несколько решений, но они в основном выглядят для V2 API или просто не так уж хороши. В идеале мне бы хотелось найти решение, в котором вы нажимаете какой-то маркер группы, а затем показывает маркеры, сгруппированные вокруг места, в котором они все находятся.

У кого-нибудь была эта проблема или что-то подобное, и она хотела бы поделиться решением?

Ответ 1

Посмотрите OverlappingMarkerSpiderfier.
Там есть демонстрационная страница, но они не показывают маркеры, которые находятся точно в одном месте, только некоторые из них очень близки друг к другу.

Но пример реальной жизни с маркерами на одном и том же месте можно увидеть на http://www.ejw.de/ejw-vor-ort/ (прокрутите вниз по карте и нажмите несколько маркеров, чтобы увидеть эффект паука).

Это идеальное решение для вашей проблемы.

Ответ 2

Смещение маркеров не является реальным решением, если они расположены в одном здании. Что вы можете сделать, так это изменить markerclusterer.js следующим образом:

  • Добавить метод прототип-щелчка в классе MarkerClusterer, например: мы переопределим это позже в функции initialize():

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    
  • В классе ClusterIcon добавьте следующий код ПОСЛЕ запуска запуска 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);
    }
    
  • Затем в вашей функции initialize(), где вы инициализируете карту и объявляете свой объект MarkerClusterer:

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

    Где multiChoice() - ВАША (еще не написанная) функция, чтобы всплывать InfoWindow со списком опций для выбора. Обратите внимание, что объект markerClusterer передается вашей функции, потому что вам понадобится это, чтобы определить, сколько маркеров в этом кластере. Например:

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

Ответ 3

Я использовал это рядом с jQuery, и он выполняет задание:

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

Ответ 4

Развернувшись на Chaoley answer, я реализовал функцию, которая, учитывая список местоположений (объекты с объектами lng и lat), координаты которых точно соответствуют то же самое немного удаляет их из своего первоначального местоположения (модифицируя объекты на месте). Затем они образуют хороший круг вокруг центральной точки.

Я обнаружил, что для моей широты (52 градуса северной широты) 0,0003 градуса радиуса окружности работают лучше всего, и что вы должны компенсировать разницу между градусами широты и долготы при преобразовании в километры. Вы можете найти приблизительные конверсии для своей широты здесь.

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

Ответ 5

@Ignatius самый отличный ответ, обновленный для работы с v2.0.7 MarkerClustererPlus.

  • Добавить метод прототип-щелчка в классе MarkerClusterer, например: мы переопределим это позже в функции initialize():

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
    
  • В классе ClusterIcon добавьте следующий код ПОСЛЕ триггера 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
    
  • Затем в вашей функции initialize(), где вы инициализируете карту и объявляете свой объект MarkerClusterer:

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

    Где multiChoice() - ВАША (еще не написанная) функция, чтобы всплывать InfoWindow со списком опций для выбора. Обратите внимание, что объект markerClusterer передается вашей функции, потому что вам понадобится это, чтобы определить, сколько маркеров в этом кластере. Например:

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

Ответ 6

Ответы выше более элегантны, но я нашел быстрый и грязный способ, который действительно работает действительно невероятно хорошо. Вы можете увидеть его в действии на www.buildinglit.com

Все, что я сделал, это добавить случайное смещение к широте и долготе на мою страницу genxml.php, чтобы каждый раз при каждом создании карты с маркерами каждый раз при каждом смещении возвращались несколько разные результаты. Это звучит как взломать, но на самом деле вам нужны только маркеры, чтобы слегка подтолкнуть в случайном направлении, чтобы они могли быть нажимаемы на карте, если они перекрываются. Это действительно работает очень хорошо, я бы сказал лучше, чем метод пауков, потому что кто хочет справиться с этой сложностью и иметь их spring везде. Вы просто хотите, чтобы можно было выбрать маркер. Подталкивание в случайном порядке отлично работает.

Вот пример выполнения итерации while node в моем 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);

Обратите внимание, что под lat и long есть смещение+. из двух перечисленных выше переменных. Мне пришлось разделить случайное число на 0,1000 на 10000000, чтобы получить десятичное число, которое было достаточно маленьким, чтобы просто перемещать маркеры. Не стесняйтесь возиться с этой переменной, чтобы получить то, что более точно для ваших нужд.

Ответ 7

В ситуациях, когда в одном здании есть несколько служб, вы можете немного уменьшить маркеры (скажем, на 0,001 градуса) в радиусе от фактической точки. Это также должно создать приятный визуальный эффект.

Ответ 8

Отметьте Marker Clusterer для V3 - эта библиотека кластерирует соседние точки в маркер группы. Карта масштабируется при щелчке кластеров. Я бы подумал, что при увеличении масштаба у вас все равно будет проблема с маркерами на том же месте.

Ответ 9

Обновлено для работы с 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

Ответ 10

Мне нравятся простые решения, поэтому здесь мои. Вместо того, чтобы модифицировать lib, что затруднит его выполнение. вы можете просто посмотреть это событие

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

тогда вы можете управлять им на

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

i, т.е. используется бутстрап, чтобы показать панель со списком. которые я нахожу гораздо более удобными и удобными, чем пауки в "переполненных" местах. (если вы используете вероятность кластера, вы столкнетесь с коллизиями, как только вы spiderfy). вы также можете проверить масштабирование.

кстати. я просто нашел листок и, похоже, работает намного лучше, кластер И spiderfy работает очень плавно http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html и это с открытым исходным кодом.

Ответ 11

Это скорее "быстрое и грязное" решение, аналогичное тому, которое предлагает Мэтью Фокс, на этот раз с использованием JavaScript.

В JavaScript вы можете просто компенсировать лат и длительность всех ваших местоположений, добавив небольшое случайное смещение для обоих, например.

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

(я обнаружил, что деление на 25000 дает достаточно разделения, но не значительно перемещает маркер из точного местоположения, например, определенного адреса).

Это делает достаточно хорошую работу, чтобы компенсировать их друг от друга, но только после того, как вы приблизились. При уменьшении масштаба все еще не ясно, что для местоположения существует несколько параметров.

Ответ 12

Отметьте это: https://github.com/plank/MarkerClusterer

Это измененный MarkerCluster, чтобы иметь указатель infoWindow в марке кластера, когда у вас есть несколько маркеров в одной и той же позиции.

Вы можете увидеть, как он работает здесь: http://culturedays.ca/en/2013-activities

Ответ 13

Развернувшись на приведенных выше ответах, просто убедитесь, что вы устанавливаете параметр maxZoom при инициализации объекта карты.

Ответ 14

Давление смещения сделает маркеры далекими, когда пользователь увеличит масштаб до макс. Поэтому я нашел способ добиться этого. это может быть не совсем правильно, но это сработало очень хорошо.

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

установите zIndex маркера так, чтобы вы увидели значок маркера, который вы хотите увидеть сверху, иначе он будет анимировать маркеры, такие как автоматическая замена. когда пользователь нажимает маркерную маркеру zIndex, чтобы принести маркер сверху, используя zIndex Swap.

Ответ 15

Как сойти с рук.. [Swift]

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

Новый маркер собирается быть создан? проверить clusterArray и манипулировать им смещением

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


    }

результат

enter image description here

Ответ 16

Добавляя к подлому гениальному ответу Мэтью Фокса, я добавил небольшое случайное смещение для каждого широты и lng при настройке объекта маркера. Например:

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

Ответ 17

Расширение ответов выше, когда вы получили присоединенные строки, не добавляете/вычитаете позицию (например, "37.12340-0.00069" ), конвертируйте свою оригинальную широту/долготу в поплавки, например. используя parseFloat(), затем добавьте или вычтите исправления.

Ответ 18

Настоящий ИТ-специалист не будет манипулировать данными, такими как.

Я видел карты с маркерами групп, щелкнув по маркеру группы, открывается информационное окно со списком членов в этом геокоде, каждый из которых можно кликать на новое значение для этого конкретного члена.

Лучший пример, который у меня есть, обрабатывает его данные на стороне Google, а не их собственные.