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


115

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

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

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

Ответы:


116

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

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

Кажется, это идеальное решение вашей проблемы.


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

При использовании OverlappingMarkerSpiderfierв сочетании с MarkerClusterer хорошим вариантом является установка maxZoomпараметра в конструкторе кластера. Это «отключает кластеры» маркеров после указанного уровня масштабирования.
Diederik

@Tad Я не знаю, являетесь ли вы разработчиком этого веб-сайта, но я хотел сообщить вам, что карта на ejw.de не работает. Я получаю ошибку JS.
Alex

Я проверил предложенную демонстрацию, но она кажется мертвой. После дополнительных исследований я нашел этот пример того, как интегрировать кластеры маркеров и Spiderifier. Нет живой демонстрации, просто клонируйте и следуйте инструкциям. github.com/yagoferrer/markerclusterer-plus-spiderfier-example
Sax

34

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

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

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
  2. В классе 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);
    }
  3. Затем в вашей функции 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;
    }

17

Я использовал это вместе с 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'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 отлично работает для меня :) Именно то, что я искал с тех пор: p
Jicao

Красивое и элегантное решение. Спасибо!
Concept211

Идеальное решение, немного компенсирующее! Возможно ли, что мы
наводим

14

Расширяя ответ Чаоли , я реализовал функцию, которая, учитывая список местоположений (объекты с 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;
    }
};

1
DzinX, я потратил последние 4 часа, пытаясь понять, как я могу безрезультатно внедрить ваш код в свой код. Я пришел к выводу, что мне это плохо, и я был бы очень признателен за вашу помощь. Вот где я пытаюсь вставить код: pastebin.com/5qYbvybm - ЛЮБАЯ ПОМОЩЬ будет признательна! Спасибо!
WebMW 09

WebMW: после извлечения маркеров из XML вы должны сначала сгруппировать их в списки, чтобы каждый список содержал маркеры, указывающие точно на одно и то же место. Затем для каждого списка, содержащего более одного элемента, запустите rightLocList (). Только после этого создайте объекты google.maps.Marker.
DzinX 09

Это не работает .... Он просто меняет мои широты с 37.68625400000000000,-75.71669800000000000на 37.686254,-75.716698тоже, что одинаково для всех значений ... Я ожидаю чего-то вроде ... 37.68625400000000000,-75.71669800000000000на ... и 37.6862540000001234,-75.7166980000001234 37.6862540000005678,-75.7166980000005678т. Д. Я также пытался изменить свой угол.
Premshankar Tiwari

Хммм, давайте немного покопаемся и будем надеяться, что @DzinX все еще здесь. Можете ли вы, товарищ (или кто-нибудь еще) объяснить, как вы получаете все эти числа? Lng_radius и т. Д.? Большое спасибо. :)
Vae

1
@Vae: Радиус - это то, насколько большим должен быть круг. Вы хотите, чтобы круг выглядел в пикселях, поэтому вам нужно знать, в вашем местоположении, какова пропорция 1 градуса широты к 1 градусу долготы (в пикселях на карте). Вы можете найти это на некоторых веб-сайтах или просто поэкспериментировать, пока не получите правильную цифру. Угол - это то, насколько первый штифт находится справа от вершины.
DzinX

11

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

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

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
  2. В классе 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
  3. Затем в вашей функции 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;
    };

1
Спасибо, отлично работает! Что касается вашей maxZoomпроблемы, я думаю, что это проблема, только если maxZoom не установлен или maxZoom для кластера больше, чем масштаб карты. Я заменил ваш жестко запрограммированный на этот фрагмент, и, похоже, он отлично работает:var maxZoom = mc.getMaxZoom(); if (null === maxZoom || maxZoom > mc.getMap().maxZoom) { maxZoom = mc.getMap().maxZoom; if (null === maxZoom) { maxZoom = 15; } }
Паскаль

Я знаю, что это действительно стар, но я пытаюсь использовать это решение. У меня он работает, но в случае отображения информационного окна с данными кластера ... как мне получить позицию маркера кластера, который был нажат в первую очередь, чтобы я мог показать информационное окно? markers - это мой массив данных ... но как я могу показать информационное окно на значке / маркере кластера?
user756659

@ user756659 в multiChoice вы получаете широту / долготу через: clickedCluster.center_.lat()/clickedCluster.center_.lng()
Йенс Коль

1
@Pascal и с небольшой запутанностью мы получаем это, что могло бы работать, но, как говорит дао python. "удобочитаемость имеет значение". var maxZoom = Math.min (mc.getMaxZoom () || 15, mc.getMap (). maxZoom || 15);
Nande

6

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

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

Вот пример создания итерационного узла оператора while в моем 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 есть смещение +. от 2 переменных выше. Мне пришлось разделить случайное число на 0,1000 на 10000000, чтобы получить десятичную дробь, которая была случайным образом достаточно маленькой, чтобы просто перемещать маркеры. Не стесняйтесь поработать с этой переменной, чтобы получить более точную для ваших нужд.


Прохладно! это грязный хак, который я нашел очень полезным, не нужно возиться с кодом API Google Maps
CuongDC

3

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


3

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

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

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

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

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


1
Мне это нравится. Если вам не нужны точные репрезентативные маркеры, просто уменьшите 25000 до 250 или 25. Простое и быстрое решение.
Fid

2

Попробуйте Marker Clusterer для V3 - эта библиотека объединяет близлежащие точки в групповой маркер. Карта увеличивается при щелчке по кластерам. Я предполагаю, что при прямом увеличении у вас все еще будет та же проблема с маркерами на том же месте.


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

@Louzoid Marker Clusterer не решает проблему, как я только что обнаружил. У меня такая же проблема, у меня несколько компаний в одном здании, поэтому отображается только 1 маркер.
Роб

1

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

Этот подход требует, чтобы пользователь щелкнул значок кластера, и не будет охватывать случаи использования, когда навигация выполняется с помощью ползунка масштабирования или прокрутки мыши. Есть идеи, как решить эти проблемы?
Remi Sture

1

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

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

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

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

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

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



0

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


0

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

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


0

Как это сойти с рук .. [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)
        }


    }

результат

введите описание изображения здесь


0

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.